From 36d83551aa3ae00d021ae57f215e92907296dff0 Mon Sep 17 00:00:00 2001 From: tajniak81 <13187254+tajniak81@users.noreply.github.com> Date: Thu, 22 Oct 2026 20:33:21 +0200 Subject: [PATCH] Docker-AIO: PocketBase images with SeaweedFS for media 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 --- Docker-AIO/.env.example | 30 +++ Docker-AIO/Dockerfile.PocketBase.SeaweedFS | 147 ++++++++++++++ .../Dockerfile.PocketBase.SeaweedFS.split | 159 +++++++++++++++ Docker-AIO/README.md | 58 +++++- ...cker-compose.pocketbase.seaweedfs.prod.yml | 64 ++++++ ...ompose.pocketbase.seaweedfs.split.prod.yml | 76 ++++++++ ...ker-compose.pocketbase.seaweedfs.split.yml | 75 ++++++++ .../docker-compose.pocketbase.seaweedfs.yml | 62 ++++++ Docker-AIO/entrypoint.sh | 182 +++++++++++++----- 9 files changed, 804 insertions(+), 49 deletions(-) create mode 100644 Docker-AIO/Dockerfile.PocketBase.SeaweedFS create mode 100644 Docker-AIO/Dockerfile.PocketBase.SeaweedFS.split create mode 100644 Docker-AIO/docker-compose.pocketbase.seaweedfs.prod.yml create mode 100644 Docker-AIO/docker-compose.pocketbase.seaweedfs.split.prod.yml create mode 100644 Docker-AIO/docker-compose.pocketbase.seaweedfs.split.yml create mode 100644 Docker-AIO/docker-compose.pocketbase.seaweedfs.yml diff --git a/Docker-AIO/.env.example b/Docker-AIO/.env.example index 00041f5..26852f4 100644 --- a/Docker-AIO/.env.example +++ b/Docker-AIO/.env.example @@ -33,6 +33,31 @@ WARP_VERSION=latest WARP_AIO_ADMIN=true WARP_AIO_WEB=true +# ------------------------------------------------------------- seaweedfs + +# docker-compose.pocketbase.seaweedfs*.yml only; set WARP_PB_S3_ACCESS_KEY +# and WARP_PB_S3_SECRET below as well. +# +# The S3 gateway's host port, on loopback unless the bind address says +# otherwise. The homeserver reaches it inside the container; this is for +# aws-cli and the like. +SEAWEED_S3_BIND=127.0.0.1 +SEAWEED_S3_PORT=8333 + +# The split file only: SeaweedFS's admin UI. It can mint credentials for +# the bucket, so the password is required. Put it behind a reverse proxy +# before binding it to 0.0.0.0. The read-only login is optional. +SEAWEED_ADMIN_BIND=127.0.0.1 +SEAWEED_ADMIN_PORT=23646 +SEAWEED_ADMIN_USER=admin +SEAWEED_ADMIN_PASSWORD= +SEAWEED_ADMIN_READONLY_USER= +SEAWEED_ADMIN_READONLY_PASSWORD= + +# Building only: the SeaweedFS release to bake in. Leave it commented out to +# keep the Dockerfile's pin; an empty value breaks the build. +# SEAWEED_VERSION=4.45 + # ------------------------------------------------------------ homeserver # Part of every user and room ID: it cannot be changed later. @@ -89,6 +114,11 @@ WARP_DB_AUTO_MIGRATE= # docker-compose.pocketbase*.yml only: keep media in an S3 bucket instead # of pb_data/storage. Bucket, region, endpoint and keys are all required # when it is on. +# +# The pocketbase.seaweedfs files turn it on themselves, against the +# SeaweedFS in the container; there only the access key and secret are +# needed (required: they are the gateway's credentials too), and the bucket +# defaults to warp. WARP_PB_S3_ENABLED=false WARP_PB_S3_BUCKET= WARP_PB_S3_REGION= diff --git a/Docker-AIO/Dockerfile.PocketBase.SeaweedFS b/Docker-AIO/Dockerfile.PocketBase.SeaweedFS new file mode 100644 index 0000000..358cba7 --- /dev/null +++ b/Docker-AIO/Dockerfile.PocketBase.SeaweedFS @@ -0,0 +1,147 @@ +# warp-aio-pocketbase-seaweedfs — the homeserver, the admin panel and the +# web client on PocketBase, with SeaweedFS in the same image as the media +# store: one `weed server -s3` process, master, volume, filer and S3 gateway +# in one. The homeserver keeps its database in /data/pb_data and its media +# in the bucket WARP_PB_S3_BUCKET (default warp) on the gateway at +# 127.0.0.1:8333. For per-role processes and SeaweedFS's admin UI, see +# Dockerfile.PocketBase.SeaweedFS.split. +# +# Build from the repository root: +# +# docker build -t warp-aio-pocketbase-seaweedfs -f Docker-AIO/Dockerfile.PocketBase.SeaweedFS . +# docker run -p 8008:8008 -p 8009:8009 -p 8080:8080 -p 127.0.0.1:8333:8333 \ +# -v warp-data:/data -v seaweed-data:/seaweed/data \ +# -e WARP_SERVER_NAME=warp.example \ +# -e WARP_PB_S3_ACCESS_KEY=warp -e WARP_PB_S3_SECRET=change-me \ +# warp-aio-pocketbase-seaweedfs +# +# WARP_PB_S3_ACCESS_KEY and WARP_PB_S3_SECRET are required: they are the +# gateway's only identity and what the homeserver signs its requests with. +# +# Ports inside the container (publish 8333 on loopback at most; never 8081, +# the volume server, which serves content by id with no authentication): +# +# 8008 homeserver 8333 SeaweedFS S3 gateway +# 8009 admin panel 8888 SeaweedFS filer +# 8080 web client 9333 SeaweedFS master +# 8081 SeaweedFS volume (not its usual 8080, which is the web client) + +# SeaweedFS release to bake in, pinned so a rebuild later brings up the same +# one; --build-arg SEAWEED_VERSION=... to upgrade. Global, because a FROM uses +# it. +ARG SEAWEED_VERSION="4.45" + +FROM node:22-alpine AS web + +WORKDIR /src +RUN corepack enable + +# Lockfile and manifests first, so the package store survives source changes. +COPY package.json pnpm-workspace.yaml pnpm-lock.yaml ./ +COPY apps/ui/package.json ./apps/ui/ +COPY apps/admin/package.json ./apps/admin/ +COPY apps/web/package.json ./apps/web/ +RUN pnpm install --frozen-lockfile --filter @warp/admin... --filter @warp/web... + +COPY apps/ui ./apps/ui +COPY apps/admin ./apps/admin +COPY apps/web ./apps/web +RUN pnpm --filter @warp/admin build && pnpm --filter @warp/web build + + +FROM golang:1.26-alpine AS build + +ARG VERSION=0.0.0-dev +ARG COMMIT=unknown +ARG BUILD_DATE=unknown +ENV CGO_ENABLED=0 + +WORKDIR /src + +# The homeserver. Dependencies first, so the module cache survives source +# changes. +COPY server/go.mod server/go.sum ./server/ +RUN cd server && go mod download +COPY server ./server +RUN cd server && go build -trimpath \ + -ldflags "-s -w \ + -X github.com/dariuszjaniak/warp/server/internal/buildinfo.Version=${VERSION} \ + -X github.com/dariuszjaniak/warp/server/internal/buildinfo.Commit=${COMMIT} \ + -X github.com/dariuszjaniak/warp/server/internal/buildinfo.BuildDate=${BUILD_DATE}" \ + -o /out/warp ./cmd/warp + +# The admin panel and the web client, each with its frontend embedded. +COPY apps/admin/go.mod ./apps/admin/ +COPY apps/admin/cmd ./apps/admin/cmd +COPY apps/admin/internal ./apps/admin/internal +COPY --from=web /src/apps/admin/internal/panel/build/dist ./apps/admin/internal/panel/build/dist +RUN cd apps/admin && GOWORK=off go build -trimpath -ldflags "-s -w -X main.version=${VERSION}" \ + -o /out/warp-admin ./cmd/warp-admin + +COPY apps/web/go.mod ./apps/web/ +COPY apps/web/cmd ./apps/web/cmd +COPY apps/web/host ./apps/web/host +COPY apps/web/client/client.go ./apps/web/client/ +COPY --from=web /src/apps/web/client/build/dist ./apps/web/client/build/dist +RUN cd apps/web && GOWORK=off go build -trimpath -ldflags "-s -w -X main.version=${VERSION}" \ + -o /out/warp-web ./cmd/warp-web + + +# The one static weed binary from the official image. +FROM chrislusf/seaweedfs:${SEAWEED_VERSION} AS seaweed + + +FROM alpine:3.21 + +# tini is PID 1 so signals reach the entrypoint and zombies are reaped. +RUN apk add --no-cache ca-certificates tzdata curl tini && \ + adduser -D -u 10001 -h /data warp + +COPY --from=build /out/warp /out/warp-admin /out/warp-web /usr/local/bin/ +COPY --from=seaweed /usr/bin/weed /usr/local/bin/weed +COPY warp.yaml.example /etc/warp/warp.yaml.example +COPY --chmod=755 Docker-AIO/entrypoint.sh /usr/local/bin/warp-aio + +# /data: the database, signing key and anything stored before S3 was on. +# /seaweed/data: everything SeaweedFS keeps. +# Mount a volume on each; a fresh named volume inherits the ownership. +RUN mkdir -p /data /seaweed/data && chown -R warp:warp /data /seaweed +WORKDIR /data +USER warp + +# Media goes to the bucket on the gateway inside this image: a fixed +# loopback address, path style because a self-hosted gateway has no +# per-bucket DNS, and a region SeaweedFS ignores. Media stored on the +# filesystem before S3 was on is not moved into the bucket. +ENV WARP_LISTEN_ADDRESS=":8008" \ + WARP_PRIVATE_KEY="/data/signing.key" \ + WARP_DB_DRIVER="pocketbase" \ + WARP_DB_CONNECTION_STRING="/data/pb_data" \ + WARP_PB_S3_ENABLED="true" \ + WARP_PB_S3_ENDPOINT="http://127.0.0.1:8333" \ + WARP_PB_S3_BUCKET="warp" \ + WARP_PB_S3_REGION="us-east-1" \ + WARP_PB_S3_FORCE_PATH_STYLE="true" \ + WARP_LOG_FORMAT="json" \ + WARP_ADMIN_LISTEN=":8009" \ + WARP_ADMIN_HOMESERVER="http://127.0.0.1:8008" \ + WARP_ADMIN_LOG_FORMAT="json" \ + WARP_WEB_LISTEN=":8080" \ + WARP_WEB_HOMESERVER="http://127.0.0.1:8008" \ + WARP_WEB_LOG_FORMAT="json" \ + WARP_AIO_ADMIN="true" \ + WARP_AIO_WEB="true" \ + WARP_AIO_EMBEDDED_POSTGRES="false" \ + WARP_AIO_SEAWEEDFS="server" + +EXPOSE 8008 8009 8080 8333 + +# Every process must answer, so a wedged one shows in `docker ps`. +HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ + CMD curl -fsS -o /dev/null http://127.0.0.1:9333/cluster/status && \ + curl -fsS -o /dev/null http://127.0.0.1:8333/healthz && \ + curl -fsS -o /dev/null http://127.0.0.1:8008/healthz && \ + { [ "$WARP_AIO_ADMIN" != true ] || curl -fsS -o /dev/null http://127.0.0.1:8009/healthz; } && \ + { [ "$WARP_AIO_WEB" != true ] || curl -fsS -o /dev/null http://127.0.0.1:8080/healthz; } || exit 1 + +ENTRYPOINT ["/sbin/tini", "--", "warp-aio"] diff --git a/Docker-AIO/Dockerfile.PocketBase.SeaweedFS.split b/Docker-AIO/Dockerfile.PocketBase.SeaweedFS.split new file mode 100644 index 0000000..828c8d7 --- /dev/null +++ b/Docker-AIO/Dockerfile.PocketBase.SeaweedFS.split @@ -0,0 +1,159 @@ +# warp-aio-pocketbase-seaweedfs-split — the homeserver, the admin panel and +# the web client on PocketBase, with SeaweedFS in the same image as the +# media store, split into its roles: master, volume, filer, S3 gateway and +# admin UI, one process each. Over Dockerfile.PocketBase.SeaweedFS that buys +# per-role restart, per-role metrics ports and the admin UI, where S3 +# identities are minted and revoked under Object Store → Users. +# +# The homeserver's identity lives in the filer's IAM store, seeded on every +# boot, the same store the admin UI writes; so a key added there never +# displaces the homeserver's. /seaweed/data has the layout `weed server -dir` +# writes, so it moves between the two SeaweedFS images unchanged. +# +# Build from the repository root: +# +# docker build -t warp-aio-pocketbase-seaweedfs-split -f Docker-AIO/Dockerfile.PocketBase.SeaweedFS.split . +# docker run -p 8008:8008 -p 8009:8009 -p 8080:8080 \ +# -p 127.0.0.1:8333:8333 -p 127.0.0.1:23646:23646 \ +# -v warp-data:/data -v seaweed-data:/seaweed/data -v seaweed-admin:/seaweed/admin \ +# -e WARP_SERVER_NAME=warp.example \ +# -e WARP_PB_S3_ACCESS_KEY=warp -e WARP_PB_S3_SECRET=change-me \ +# -e WEED_ADMIN_PASSWORD=change-me \ +# warp-aio-pocketbase-seaweedfs-split +# +# WARP_PB_S3_ACCESS_KEY, WARP_PB_S3_SECRET and WEED_ADMIN_PASSWORD are +# required; the container refuses to start an open gateway or admin UI. +# +# Ports inside the container (publish 8333 and 23646 on loopback at most; +# master, volume and filer listen on loopback only): +# +# 8008 homeserver 8333 SeaweedFS S3 gateway +# 8009 admin panel 8888 SeaweedFS filer +# 8080 web client 9333 SeaweedFS master +# 8081 SeaweedFS volume 23646 SeaweedFS admin UI +# 9324-9328 SeaweedFS metrics: master, volume, filer, S3, admin + +# SeaweedFS release to bake in, pinned so a rebuild later brings up the same +# one; --build-arg SEAWEED_VERSION=... to upgrade. Global, because a FROM uses +# it. +ARG SEAWEED_VERSION="4.45" + +FROM node:22-alpine AS web + +WORKDIR /src +RUN corepack enable + +# Lockfile and manifests first, so the package store survives source changes. +COPY package.json pnpm-workspace.yaml pnpm-lock.yaml ./ +COPY apps/ui/package.json ./apps/ui/ +COPY apps/admin/package.json ./apps/admin/ +COPY apps/web/package.json ./apps/web/ +RUN pnpm install --frozen-lockfile --filter @warp/admin... --filter @warp/web... + +COPY apps/ui ./apps/ui +COPY apps/admin ./apps/admin +COPY apps/web ./apps/web +RUN pnpm --filter @warp/admin build && pnpm --filter @warp/web build + + +FROM golang:1.26-alpine AS build + +ARG VERSION=0.0.0-dev +ARG COMMIT=unknown +ARG BUILD_DATE=unknown +ENV CGO_ENABLED=0 + +WORKDIR /src + +# The homeserver. Dependencies first, so the module cache survives source +# changes. +COPY server/go.mod server/go.sum ./server/ +RUN cd server && go mod download +COPY server ./server +RUN cd server && go build -trimpath \ + -ldflags "-s -w \ + -X github.com/dariuszjaniak/warp/server/internal/buildinfo.Version=${VERSION} \ + -X github.com/dariuszjaniak/warp/server/internal/buildinfo.Commit=${COMMIT} \ + -X github.com/dariuszjaniak/warp/server/internal/buildinfo.BuildDate=${BUILD_DATE}" \ + -o /out/warp ./cmd/warp + +# The admin panel and the web client, each with its frontend embedded. +COPY apps/admin/go.mod ./apps/admin/ +COPY apps/admin/cmd ./apps/admin/cmd +COPY apps/admin/internal ./apps/admin/internal +COPY --from=web /src/apps/admin/internal/panel/build/dist ./apps/admin/internal/panel/build/dist +RUN cd apps/admin && GOWORK=off go build -trimpath -ldflags "-s -w -X main.version=${VERSION}" \ + -o /out/warp-admin ./cmd/warp-admin + +COPY apps/web/go.mod ./apps/web/ +COPY apps/web/cmd ./apps/web/cmd +COPY apps/web/host ./apps/web/host +COPY apps/web/client/client.go ./apps/web/client/ +COPY --from=web /src/apps/web/client/build/dist ./apps/web/client/build/dist +RUN cd apps/web && GOWORK=off go build -trimpath -ldflags "-s -w -X main.version=${VERSION}" \ + -o /out/warp-web ./cmd/warp-web + + +# The one static weed binary from the official image. +FROM chrislusf/seaweedfs:${SEAWEED_VERSION} AS seaweed + + +FROM alpine:3.21 + +# tini is PID 1 so signals reach the entrypoint and zombies are reaped. +RUN apk add --no-cache ca-certificates tzdata curl tini && \ + adduser -D -u 10001 -h /data warp + +COPY --from=build /out/warp /out/warp-admin /out/warp-web /usr/local/bin/ +COPY --from=seaweed /usr/bin/weed /usr/local/bin/weed +COPY warp.yaml.example /etc/warp/warp.yaml.example +COPY --chmod=755 Docker-AIO/entrypoint.sh /usr/local/bin/warp-aio + +# /data: the database, signing key and anything stored before S3 was on. +# /seaweed/data: master, volume and filer, sharing it. +# /seaweed/admin: the admin UI's session key and maintenance state. +# Mount a volume on each; a fresh named volume inherits the ownership. +RUN mkdir -p /data /seaweed/data /seaweed/admin && chown -R warp:warp /data /seaweed +WORKDIR /data +USER warp + +# Media goes to the bucket on the gateway inside this image: a fixed +# loopback address, path style because a self-hosted gateway has no +# per-bucket DNS, and a region SeaweedFS ignores. Media stored on the +# filesystem before S3 was on is not moved into the bucket. +ENV WARP_LISTEN_ADDRESS=":8008" \ + WARP_PRIVATE_KEY="/data/signing.key" \ + WARP_DB_DRIVER="pocketbase" \ + WARP_DB_CONNECTION_STRING="/data/pb_data" \ + WARP_PB_S3_ENABLED="true" \ + WARP_PB_S3_ENDPOINT="http://127.0.0.1:8333" \ + WARP_PB_S3_BUCKET="warp" \ + WARP_PB_S3_REGION="us-east-1" \ + WARP_PB_S3_FORCE_PATH_STYLE="true" \ + WARP_LOG_FORMAT="json" \ + WARP_ADMIN_LISTEN=":8009" \ + WARP_ADMIN_HOMESERVER="http://127.0.0.1:8008" \ + WARP_ADMIN_LOG_FORMAT="json" \ + WARP_WEB_LISTEN=":8080" \ + WARP_WEB_HOMESERVER="http://127.0.0.1:8008" \ + WARP_WEB_LOG_FORMAT="json" \ + WARP_AIO_ADMIN="true" \ + WARP_AIO_WEB="true" \ + WARP_AIO_EMBEDDED_POSTGRES="false" \ + WARP_AIO_SEAWEEDFS="split" \ + WEED_ADMIN_USER="admin" + +EXPOSE 8008 8009 8080 8333 23646 + +# Every process must answer, so a wedged one shows in `docker ps`. +HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ + CMD curl -fsS -o /dev/null http://127.0.0.1:9333/cluster/status && \ + curl -fsS -o /dev/null http://127.0.0.1:8081/healthz && \ + curl -fsS -o /dev/null http://127.0.0.1:8888/healthz && \ + curl -fsS -o /dev/null http://127.0.0.1:8333/healthz && \ + curl -fsS -o /dev/null http://127.0.0.1:23646/health && \ + curl -fsS -o /dev/null http://127.0.0.1:8008/healthz && \ + { [ "$WARP_AIO_ADMIN" != true ] || curl -fsS -o /dev/null http://127.0.0.1:8009/healthz; } && \ + { [ "$WARP_AIO_WEB" != true ] || curl -fsS -o /dev/null http://127.0.0.1:8080/healthz; } || exit 1 + +ENTRYPOINT ["/sbin/tini", "--", "warp-aio"] diff --git a/Docker-AIO/README.md b/Docker-AIO/README.md index 9662fe1..00da281 100644 --- a/Docker-AIO/README.md +++ b/Docker-AIO/README.md @@ -10,15 +10,21 @@ policy brings the set back. | [`Dockerfile`](Dockerfile) | `warp-aio` | SQLite, `/data/warp.db` | | [`Dockerfile.PocketBase`](Dockerfile.PocketBase) | `warp-aio-pocketbase` | PocketBase (embedded driver), `/data/pb_data` | | [`Dockerfile.PostgreSQL`](Dockerfile.PostgreSQL) | `warp-aio-postgresql` | PostgreSQL 17 in the container, `/data/postgres` | +| [`Dockerfile.PocketBase.SeaweedFS`](Dockerfile.PocketBase.SeaweedFS) | `warp-aio-pocketbase-seaweedfs` | PocketBase, media in SeaweedFS (`weed server -s3`) in the container | +| [`Dockerfile.PocketBase.SeaweedFS.split`](Dockerfile.PocketBase.SeaweedFS.split) | `warp-aio-pocketbase-seaweedfs-split` | PocketBase, media in SeaweedFS split into its roles, with its admin UI | | Compose file | Image | | --- | --- | | `docker-compose.sqlite.yml` | `warp-aio`, built here | | `docker-compose.pocketbase.yml` | `warp-aio-pocketbase`, built here | | `docker-compose.postgresql.yml` | `warp-aio-postgresql`, built here | +| `docker-compose.pocketbase.seaweedfs.yml` | `warp-aio-pocketbase-seaweedfs`, built here | +| `docker-compose.pocketbase.seaweedfs.split.yml` | `warp-aio-pocketbase-seaweedfs-split`, built here | | `docker-compose.*.prod.yml` | the same images, pulled, never built | -Everything lives in the `warp-data` volume, mounted on `/data`. +Everything lives in the `warp-data` volume, mounted on `/data`; the +SeaweedFS images add `seaweed-data` on `/seaweed/data` (and the split one +`seaweed-admin` on `/seaweed/admin`). ```sh cd Docker-AIO @@ -34,9 +40,48 @@ inside the container, so it needs no password and is not reachable from outside. To point that image at a PostgreSQL elsewhere instead, set `WARP_AIO_EMBEDDED_POSTGRES=false` and `WARP_DB_CONNECTION_STRING`. -The three Dockerfiles share their build stages line for line; only the -header and the final stage differ. Keep the build stages in step when -changing one. +The Dockerfiles share their build stages line for line; only the header, +the SeaweedFS stage and the final stage differ. Keep the build stages in +step when changing one. + +## SeaweedFS + +The two SeaweedFS images keep the PocketBase database in `/data/pb_data` +and put media in an S3 bucket (`WARP_PB_S3_BUCKET`, default `warp`) on a +SeaweedFS inside the same container. The compose files point the +homeserver at it; `.env` only has to give it credentials: + +```sh +cp .env.example .env # set WARP_PB_S3_ACCESS_KEY, WARP_PB_S3_SECRET + # (and SEAWEED_ADMIN_PASSWORD for split) +docker compose -f docker-compose.pocketbase.seaweedfs.yml up -d --build +``` + +- **`Dockerfile.PocketBase.SeaweedFS`** runs `weed server -s3`: master, + volume, filer and S3 gateway in one process. The gateway has exactly + one identity, the homeserver's key pair. +- **`Dockerfile.PocketBase.SeaweedFS.split`** runs each role as its own + process, plus SeaweedFS's admin UI on port 23646, where further S3 + identities are created and revoked under *Object Store → Users*. The + homeserver's identity is written into the filer's IAM store on every + boot (the store the admin UI writes too), so a key added there never + displaces it, and a rotated `WARP_PB_S3_SECRET` lands on the next start. + +Both refuse to start without the key pair, and the split one without +`SEAWEED_ADMIN_PASSWORD`: either would otherwise serve an open gateway or +an open panel that can mint credentials. The S3 gateway and the admin UI +are published on 127.0.0.1 only; the volume server, which serves content +by id with no authentication, is never published. Inside, it is on 8081, +since 8080 is the web client. + +Both write `/seaweed/data` in the layout `weed server -dir` uses, and their +compose files share a project and volumes, so switching between them +needs no migration. + +Media already stored on the filesystem is not moved into the bucket when +S3 is turned on, so start these on a fresh `warp-data` or copy the files +across first. The SeaweedFS release is pinned in the Dockerfiles +(`SEAWEED_VERSION`, 4.45); upgrading it means a rebuild. ## Settings @@ -62,7 +107,10 @@ from the repository root: docker build -t $WARP_REGISTRY/warp-aio:$WARP_VERSION -f Docker-AIO/Dockerfile . docker build -t $WARP_REGISTRY/warp-aio-pocketbase:$WARP_VERSION -f Docker-AIO/Dockerfile.PocketBase . docker build -t $WARP_REGISTRY/warp-aio-postgresql:$WARP_VERSION -f Docker-AIO/Dockerfile.PostgreSQL . -for i in warp-aio warp-aio-pocketbase warp-aio-postgresql; do +docker build -t $WARP_REGISTRY/warp-aio-pocketbase-seaweedfs:$WARP_VERSION -f Docker-AIO/Dockerfile.PocketBase.SeaweedFS . +docker build -t $WARP_REGISTRY/warp-aio-pocketbase-seaweedfs-split:$WARP_VERSION -f Docker-AIO/Dockerfile.PocketBase.SeaweedFS.split . +for i in warp-aio warp-aio-pocketbase warp-aio-postgresql \ + warp-aio-pocketbase-seaweedfs warp-aio-pocketbase-seaweedfs-split; do docker push $WARP_REGISTRY/$i:$WARP_VERSION done ``` diff --git a/Docker-AIO/docker-compose.pocketbase.seaweedfs.prod.yml b/Docker-AIO/docker-compose.pocketbase.seaweedfs.prod.yml new file mode 100644 index 0000000..f0a2c9a --- /dev/null +++ b/Docker-AIO/docker-compose.pocketbase.seaweedfs.prod.yml @@ -0,0 +1,64 @@ +# Warp all-in-one on PocketBase with SeaweedFS: the homeserver, the admin +# panel, the web client and SeaweedFS (one `weed server -s3` process) in one +# container pulled from a registry (the image is built from +# Dockerfile.PocketBase.SeaweedFS). +# +# cd Docker-AIO +# cp .env.example .env # then edit it; set WARP_PB_S3_ACCESS_KEY +# # and WARP_PB_S3_SECRET +# docker compose -f docker-compose.pocketbase.seaweedfs.prod.yml up -d +# +# The database stays in /data/pb_data (warp-data); media goes to the bucket +# WARP_PB_S3_BUCKET in SeaweedFS (seaweed-data), whose gateway has exactly +# one identity: the homeserver's. Media stored before S3 was on is not moved +# into the bucket. +# +# Homeserver http://localhost:8008, admin panel http://localhost:8009, web +# client http://localhost:8080. The S3 gateway (8333) is published on +# loopback, for aws-cli and the like; SEAWEED_S3_BIND=0.0.0.0 exposes it. +# +# This and docker-compose.pocketbase.seaweedfs.split.yml share their project +# and volumes: SeaweedFS's data has the same layout either way, so switching +# between them needs no migration. +# +# Create the first administrator once the server is up: +# +# docker compose -f docker-compose.pocketbase.seaweedfs.prod.yml exec warp warp create-account --username admin --admin +# +# Nothing is built here: the image comes from WARP_REGISTRY (default +# ghcr.io/dariuszjaniak) at tag WARP_VERSION (default latest). Pin +# WARP_VERSION to a release in production, and update with +# +# docker compose -f docker-compose.pocketbase.seaweedfs.prod.yml pull && docker compose -f docker-compose.pocketbase.seaweedfs.prod.yml up -d + +name: warp-aio-pocketbase-seaweedfs + +services: + warp: + image: ${WARP_REGISTRY:-ghcr.io/dariuszjaniak}/warp-aio-pocketbase-seaweedfs:${WARP_VERSION:-latest} + pull_policy: always + restart: unless-stopped + env_file: .env + environment: + # The gateway is in the container at a fixed address; these override + # whatever .env says about S3. + WARP_PB_S3_ENABLED: "true" + WARP_PB_S3_ENDPOINT: http://127.0.0.1:8333 + WARP_PB_S3_BUCKET: ${WARP_PB_S3_BUCKET:-warp} + WARP_PB_S3_REGION: ${WARP_PB_S3_REGION:-us-east-1} + WARP_PB_S3_FORCE_PATH_STYLE: "true" + # The gateway's only identity, and what the homeserver signs with. + WARP_PB_S3_ACCESS_KEY: ${WARP_PB_S3_ACCESS_KEY:?set WARP_PB_S3_ACCESS_KEY in .env} + WARP_PB_S3_SECRET: ${WARP_PB_S3_SECRET:?set WARP_PB_S3_SECRET in .env} + volumes: + - warp-data:/data + - seaweed-data:/seaweed/data + ports: + - "${WARP_PORT:-8008}:8008" + - "${WARP_ADMIN_PORT:-8009}:8009" + - "${WARP_WEB_PORT:-8080}:8080" + - "${SEAWEED_S3_BIND:-127.0.0.1}:${SEAWEED_S3_PORT:-8333}:8333" + +volumes: + warp-data: + seaweed-data: diff --git a/Docker-AIO/docker-compose.pocketbase.seaweedfs.split.prod.yml b/Docker-AIO/docker-compose.pocketbase.seaweedfs.split.prod.yml new file mode 100644 index 0000000..849726d --- /dev/null +++ b/Docker-AIO/docker-compose.pocketbase.seaweedfs.split.prod.yml @@ -0,0 +1,76 @@ +# Warp all-in-one on PocketBase with SeaweedFS split into its roles: the +# homeserver, the admin panel, the web client, and SeaweedFS's master, +# volume, filer, S3 gateway and admin UI, all in one container pulled from +# a registry (the image is built from Dockerfile.PocketBase.SeaweedFS.split). +# +# cd Docker-AIO +# cp .env.example .env # then edit it; set WARP_PB_S3_ACCESS_KEY, +# # WARP_PB_S3_SECRET and SEAWEED_ADMIN_PASSWORD +# docker compose -f docker-compose.pocketbase.seaweedfs.split.prod.yml up -d +# +# The database stays in /data/pb_data (warp-data); media goes to the bucket +# WARP_PB_S3_BUCKET in SeaweedFS (seaweed-data). The homeserver's S3 identity +# is seeded into the filer's IAM store on every boot, beside any added in +# the admin UI under Object Store → Users. Media stored before S3 was on is +# not moved into the bucket. +# +# Homeserver http://localhost:8008, admin panel http://localhost:8009, web +# client http://localhost:8080. The S3 gateway (8333) and the SeaweedFS admin +# UI (23646) are published on loopback; SEAWEED_S3_BIND and +# SEAWEED_ADMIN_BIND=0.0.0.0 expose them, the admin UI only behind a proxy. +# +# This and docker-compose.pocketbase.seaweedfs.yml share their project and +# volumes: SeaweedFS's data has the same layout either way, so switching +# between them needs no migration. +# +# Create the first administrator once the server is up: +# +# docker compose -f docker-compose.pocketbase.seaweedfs.split.prod.yml exec warp warp create-account --username admin --admin +# +# Nothing is built here: the image comes from WARP_REGISTRY (default +# ghcr.io/dariuszjaniak) at tag WARP_VERSION (default latest). Pin +# WARP_VERSION to a release in production, and update with +# +# docker compose -f docker-compose.pocketbase.seaweedfs.split.prod.yml pull && docker compose -f docker-compose.pocketbase.seaweedfs.split.prod.yml up -d + +name: warp-aio-pocketbase-seaweedfs + +services: + warp: + image: ${WARP_REGISTRY:-ghcr.io/dariuszjaniak}/warp-aio-pocketbase-seaweedfs-split:${WARP_VERSION:-latest} + pull_policy: always + restart: unless-stopped + env_file: .env + environment: + # The gateway is in the container at a fixed address; these override + # whatever .env says about S3. + WARP_PB_S3_ENABLED: "true" + WARP_PB_S3_ENDPOINT: http://127.0.0.1:8333 + WARP_PB_S3_BUCKET: ${WARP_PB_S3_BUCKET:-warp} + WARP_PB_S3_REGION: ${WARP_PB_S3_REGION:-us-east-1} + WARP_PB_S3_FORCE_PATH_STYLE: "true" + # The homeserver's identity in SeaweedFS, and what it signs with. + WARP_PB_S3_ACCESS_KEY: ${WARP_PB_S3_ACCESS_KEY:?set WARP_PB_S3_ACCESS_KEY in .env} + WARP_PB_S3_SECRET: ${WARP_PB_S3_SECRET:?set WARP_PB_S3_SECRET in .env} + # The admin UI can mint bucket credentials, so it needs a password. + WEED_ADMIN_USER: ${SEAWEED_ADMIN_USER:-admin} + WEED_ADMIN_PASSWORD: ${SEAWEED_ADMIN_PASSWORD:?set SEAWEED_ADMIN_PASSWORD in .env} + WEED_ADMIN_READONLY_USER: ${SEAWEED_ADMIN_READONLY_USER:-} + WEED_ADMIN_READONLY_PASSWORD: ${SEAWEED_ADMIN_READONLY_PASSWORD:-} + volumes: + - warp-data:/data + - seaweed-data:/seaweed/data + - seaweed-admin:/seaweed/admin + ports: + - "${WARP_PORT:-8008}:8008" + - "${WARP_ADMIN_PORT:-8009}:8009" + - "${WARP_WEB_PORT:-8080}:8080" + - "${SEAWEED_S3_BIND:-127.0.0.1}:${SEAWEED_S3_PORT:-8333}:8333" + - "${SEAWEED_ADMIN_BIND:-127.0.0.1}:${SEAWEED_ADMIN_PORT:-23646}:23646" + +volumes: + warp-data: + # Master, volume and filer share it, as `weed server -dir` lays it out. + seaweed-data: + # The admin UI's session key and maintenance state; no object data. + seaweed-admin: diff --git a/Docker-AIO/docker-compose.pocketbase.seaweedfs.split.yml b/Docker-AIO/docker-compose.pocketbase.seaweedfs.split.yml new file mode 100644 index 0000000..2dcb946 --- /dev/null +++ b/Docker-AIO/docker-compose.pocketbase.seaweedfs.split.yml @@ -0,0 +1,75 @@ +# Warp all-in-one on PocketBase with SeaweedFS split into its roles: the +# homeserver, the admin panel, the web client, and SeaweedFS's master, +# volume, filer, S3 gateway and admin UI, all in one container built from +# this checkout (Dockerfile.PocketBase.SeaweedFS.split). +# +# cd Docker-AIO +# cp .env.example .env # then edit it; set WARP_PB_S3_ACCESS_KEY, +# # WARP_PB_S3_SECRET and SEAWEED_ADMIN_PASSWORD +# docker compose -f docker-compose.pocketbase.seaweedfs.split.yml up -d --build +# +# The database stays in /data/pb_data (warp-data); media goes to the bucket +# WARP_PB_S3_BUCKET in SeaweedFS (seaweed-data). The homeserver's S3 identity +# is seeded into the filer's IAM store on every boot, beside any added in +# the admin UI under Object Store → Users. Media stored before S3 was on is +# not moved into the bucket. +# +# Homeserver http://localhost:8008, admin panel http://localhost:8009, web +# client http://localhost:8080. The S3 gateway (8333) and the SeaweedFS admin +# UI (23646) are published on loopback; SEAWEED_S3_BIND and +# SEAWEED_ADMIN_BIND=0.0.0.0 expose them, the admin UI only behind a proxy. +# +# This and docker-compose.pocketbase.seaweedfs.yml share their project and +# volumes: SeaweedFS's data has the same layout either way, so switching +# between them needs no migration. +# +# Create the first administrator once the server is up: +# +# docker compose -f docker-compose.pocketbase.seaweedfs.split.yml exec warp warp create-account --username admin --admin + +name: warp-aio-pocketbase-seaweedfs + +services: + warp: + build: + context: .. + dockerfile: Docker-AIO/Dockerfile.PocketBase.SeaweedFS.split + args: + # Passed only when set, so the Dockerfile's pin stays otherwise. + - SEAWEED_VERSION + image: warp-aio-pocketbase-seaweedfs-split:latest + restart: unless-stopped + env_file: .env + environment: + # The gateway is in the container at a fixed address; these override + # whatever .env says about S3. + WARP_PB_S3_ENABLED: "true" + WARP_PB_S3_ENDPOINT: http://127.0.0.1:8333 + WARP_PB_S3_BUCKET: ${WARP_PB_S3_BUCKET:-warp} + WARP_PB_S3_REGION: ${WARP_PB_S3_REGION:-us-east-1} + WARP_PB_S3_FORCE_PATH_STYLE: "true" + # The homeserver's identity in SeaweedFS, and what it signs with. + WARP_PB_S3_ACCESS_KEY: ${WARP_PB_S3_ACCESS_KEY:?set WARP_PB_S3_ACCESS_KEY in .env} + WARP_PB_S3_SECRET: ${WARP_PB_S3_SECRET:?set WARP_PB_S3_SECRET in .env} + # The admin UI can mint bucket credentials, so it needs a password. + WEED_ADMIN_USER: ${SEAWEED_ADMIN_USER:-admin} + WEED_ADMIN_PASSWORD: ${SEAWEED_ADMIN_PASSWORD:?set SEAWEED_ADMIN_PASSWORD in .env} + WEED_ADMIN_READONLY_USER: ${SEAWEED_ADMIN_READONLY_USER:-} + WEED_ADMIN_READONLY_PASSWORD: ${SEAWEED_ADMIN_READONLY_PASSWORD:-} + volumes: + - warp-data:/data + - seaweed-data:/seaweed/data + - seaweed-admin:/seaweed/admin + ports: + - "${WARP_PORT:-8008}:8008" + - "${WARP_ADMIN_PORT:-8009}:8009" + - "${WARP_WEB_PORT:-8080}:8080" + - "${SEAWEED_S3_BIND:-127.0.0.1}:${SEAWEED_S3_PORT:-8333}:8333" + - "${SEAWEED_ADMIN_BIND:-127.0.0.1}:${SEAWEED_ADMIN_PORT:-23646}:23646" + +volumes: + warp-data: + # Master, volume and filer share it, as `weed server -dir` lays it out. + seaweed-data: + # The admin UI's session key and maintenance state; no object data. + seaweed-admin: diff --git a/Docker-AIO/docker-compose.pocketbase.seaweedfs.yml b/Docker-AIO/docker-compose.pocketbase.seaweedfs.yml new file mode 100644 index 0000000..abe31e3 --- /dev/null +++ b/Docker-AIO/docker-compose.pocketbase.seaweedfs.yml @@ -0,0 +1,62 @@ +# Warp all-in-one on PocketBase with SeaweedFS: the homeserver, the admin +# panel, the web client and SeaweedFS (one `weed server -s3` process) in one +# container built from this checkout (Dockerfile.PocketBase.SeaweedFS). +# +# cd Docker-AIO +# cp .env.example .env # then edit it; set WARP_PB_S3_ACCESS_KEY +# # and WARP_PB_S3_SECRET +# docker compose -f docker-compose.pocketbase.seaweedfs.yml up -d --build +# +# The database stays in /data/pb_data (warp-data); media goes to the bucket +# WARP_PB_S3_BUCKET in SeaweedFS (seaweed-data), whose gateway has exactly +# one identity: the homeserver's. Media stored before S3 was on is not moved +# into the bucket. +# +# Homeserver http://localhost:8008, admin panel http://localhost:8009, web +# client http://localhost:8080. The S3 gateway (8333) is published on +# loopback, for aws-cli and the like; SEAWEED_S3_BIND=0.0.0.0 exposes it. +# +# This and docker-compose.pocketbase.seaweedfs.split.yml share their project +# and volumes: SeaweedFS's data has the same layout either way, so switching +# between them needs no migration. +# +# Create the first administrator once the server is up: +# +# docker compose -f docker-compose.pocketbase.seaweedfs.yml exec warp warp create-account --username admin --admin + +name: warp-aio-pocketbase-seaweedfs + +services: + warp: + build: + context: .. + dockerfile: Docker-AIO/Dockerfile.PocketBase.SeaweedFS + args: + # Passed only when set, so the Dockerfile's pin stays otherwise. + - SEAWEED_VERSION + image: warp-aio-pocketbase-seaweedfs:latest + restart: unless-stopped + env_file: .env + environment: + # The gateway is in the container at a fixed address; these override + # whatever .env says about S3. + WARP_PB_S3_ENABLED: "true" + WARP_PB_S3_ENDPOINT: http://127.0.0.1:8333 + WARP_PB_S3_BUCKET: ${WARP_PB_S3_BUCKET:-warp} + WARP_PB_S3_REGION: ${WARP_PB_S3_REGION:-us-east-1} + WARP_PB_S3_FORCE_PATH_STYLE: "true" + # The gateway's only identity, and what the homeserver signs with. + WARP_PB_S3_ACCESS_KEY: ${WARP_PB_S3_ACCESS_KEY:?set WARP_PB_S3_ACCESS_KEY in .env} + WARP_PB_S3_SECRET: ${WARP_PB_S3_SECRET:?set WARP_PB_S3_SECRET in .env} + volumes: + - warp-data:/data + - seaweed-data:/seaweed/data + ports: + - "${WARP_PORT:-8008}:8008" + - "${WARP_ADMIN_PORT:-8009}:8009" + - "${WARP_WEB_PORT:-8080}:8080" + - "${SEAWEED_S3_BIND:-127.0.0.1}:${SEAWEED_S3_PORT:-8333}:8333" + +volumes: + warp-data: + seaweed-data: diff --git a/Docker-AIO/entrypoint.sh b/Docker-AIO/entrypoint.sh index d0c56ba..ac980f2 100644 --- a/Docker-AIO/entrypoint.sh +++ b/Docker-AIO/entrypoint.sh @@ -1,10 +1,12 @@ #!/bin/sh -# warp-aio: runs the homeserver, the admin panel and the web client (and, -# with WARP_AIO_EMBEDDED_POSTGRES=true, PostgreSQL) in one container. +# 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 of them exits, the others are stopped and the container exits, so -# the restart policy brings the whole set back. SIGTERM stops the clients -# first, then the homeserver, then PostgreSQL. +# 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`. # @@ -14,6 +16,11 @@ # $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 @@ -23,36 +30,53 @@ fi log() { echo "warp-aio: $*" >&2; } -pg_pid="" -warp_pid="" -admin_pid="" -web_pid="" +# "name:pid" of every process started, newest first, so shutdown walks it in +# reverse start order. +procs="" -# stop PID...: SIGTERM, then wait for each to exit. -stop() { - for pid in "$@"; do - [ -n "$pid" ] && kill -TERM "$pid" 2>/dev/null || true - done - for pid in "$@"; do - [ -n "$pid" ] && wait "$pid" 2>/dev/null || true - done +# run NAME COMMAND...: start COMMAND in the background and track it. +run() { + name=$1 + shift + "$@" & + procs="$name:$! $procs" } shutdown() { trap '' TERM INT - stop "$web_pid" "$admin_pid" - stop "$warp_pid" - if [ -n "$pg_pid" ]; then + for entry in $procs; do + name=${entry%%:*} + pid=${entry#*:} # SIGINT is PostgreSQL's fast shutdown: it does not wait for - # clients, and the homeserver is gone by now anyway. - kill -INT "$pg_pid" 2>/dev/null || true - wait "$pg_pid" 2>/dev/null || true - fi + # 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 -if [ "$WARP_AIO_EMBEDDED_POSTGRES" = true ]; then +# 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 @@ -64,10 +88,9 @@ if [ "$WARP_AIO_EMBEDDED_POSTGRES" = true ]; then # a socket inside this container. initdb -D "$PGDATA" -U warp --auth=trust --encoding=UTF8 --locale=C >/dev/null fi - postgres -D "$PGDATA" -c listen_addresses='' -c unix_socket_directories=/run/postgresql & - pg_pid=$! + run postgres postgres -D "$PGDATA" -c listen_addresses='' -c unix_socket_directories=/run/postgresql until pg_isready -q -h /run/postgresql -U warp; do - kill -0 "$pg_pid" 2>/dev/null || { log "PostgreSQL failed to start"; exit 1; } + check sleep 1 done if [ "$(psql -h /run/postgresql -U warp -d postgres -tAc "SELECT 1 FROM pg_database WHERE datname = 'warp'")" != 1 ]; then @@ -75,34 +98,105 @@ if [ "$WARP_AIO_EMBEDDED_POSTGRES" = true ]; then 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 -warp serve & -warp_pid=$! - -if [ "$WARP_AIO_ADMIN" = true ]; then - warp-admin & - admin_pid=$! +run warp warp serve +if [ "${WARP_AIO_ADMIN:-true}" = true ]; then + run warp-admin warp-admin fi -if [ "$WARP_AIO_WEB" = true ]; then - warp-web & - web_pid=$! +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 - for entry in "postgres:$pg_pid" "warp:$warp_pid" "warp-admin:$admin_pid" "warp-web:$web_pid"; do - pid="${entry#*:}" - if [ -n "$pid" ] && ! kill -0 "$pid" 2>/dev/null; then - log "${entry%%:*} exited; stopping the rest" - shutdown 1 - fi - done + check sleep 1 & wait $! || true done