diff --git a/Docker/.env.example b/Docker/.env.example index de6170b..ef40f9e 100644 --- a/Docker/.env.example +++ b/Docker/.env.example @@ -31,6 +31,31 @@ WARP_VERSION=latest # refuses to start without it. WARP_POSTGRES_PASSWORD=change-me +# ------------------------------------------------------------- seaweedfs + +# docker-compose.pocketbase.seaweedfs*.yml only; set WARP_PB_S3_ACCESS_KEY +# and WARP_PB_S3_SECRET below as well. +# +# The SeaweedFS image every SeaweedFS container runs. Pin it: the data +# volume outlives the image. +SEAWEED_IMAGE=chrislusf/seaweedfs:4.45 + +# The S3 gateway's host port, on loopback unless the bind address says +# otherwise. The homeserver reaches it over the compose network; 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= + # ------------------------------------------------------------ homeserver # Part of every user and room ID: it cannot be changed later. @@ -87,13 +112,23 @@ 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 their own +# SeaweedFS; there only the access key and secret are needed (required: +# they are the gateway's credentials too), and the bucket defaults to warp. +# +# The pocketbase.s3 files turn it on against a bucket elsewhere: the +# endpoint, access key and secret are required, the bucket (default warp) +# must exist, the region defaults to us-east-1 and path style to true +# (SeaweedFS, MinIO; set false for AWS S3). For a gateway on the Docker +# host, use http://host.docker.internal:8333. WARP_PB_S3_ENABLED=false WARP_PB_S3_BUCKET= WARP_PB_S3_REGION= WARP_PB_S3_ENDPOINT= WARP_PB_S3_ACCESS_KEY= WARP_PB_S3_SECRET= -WARP_PB_S3_FORCE_PATH_STYLE=false +WARP_PB_S3_FORCE_PATH_STYLE= # Client API. Defaults *, false, 0s (never expire), 0s, true. WARP_CORS_ALLOWED_ORIGINS= diff --git a/Docker/README.md b/Docker/README.md index fa8d8af..39f75e3 100644 --- a/Docker/README.md +++ b/Docker/README.md @@ -9,6 +9,9 @@ The homeserver (`warp`), the admin panel (`warp-admin`) and the web client | `docker-compose.sqlite.yml` | SQLite, in the `warp-data` volume | built here | | `docker-compose.pocketbase.yml` | PocketBase, embedded in the homeserver, in `warp-data` | built here | | `docker-compose.postgresql.yml` | PostgreSQL 17, its own container | built here | +| `docker-compose.pocketbase.s3.yml` | PocketBase, media in an existing S3 bucket elsewhere | built here | +| `docker-compose.pocketbase.seaweedfs.yml` | PocketBase, media in SeaweedFS, one container | built here | +| `docker-compose.pocketbase.seaweedfs.split.yml` | PocketBase, media in SeaweedFS, one container per role, with its admin UI | built here | | `docker-compose.*.prod.yml` | as above | pulled, never built | ```sh @@ -26,6 +29,39 @@ TLS-terminating reverse proxy in front before exposing them; see PocketBase is a database driver inside the homeserver, not a separate server, so the PocketBase files have no PocketBase container. +## Media in S3 + +The three `pocketbase.s3` and `pocketbase.seaweedfs` setups keep the +PocketBase database in `/data/pb_data` and put media in an S3 bucket +(`WARP_PB_S3_BUCKET`, default `warp`). 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. + +- **`docker-compose.pocketbase.s3.yml`**: a bucket that already exists + elsewhere (AWS, MinIO, a SeaweedFS on another host). Set + `WARP_PB_S3_ENDPOINT`, `WARP_PB_S3_ACCESS_KEY` and `WARP_PB_S3_SECRET`. + Path style is on by default; set `WARP_PB_S3_FORCE_PATH_STYLE=false` for + AWS S3. `http://host.docker.internal:8333` reaches a gateway on the + Docker host. The homeserver waits up to 30 seconds for the endpoint to + answer before it starts. +- **`docker-compose.pocketbase.seaweedfs.yml`**: SeaweedFS as one + `weed server -s3` container, plus a one-shot `seaweedfs-init` that + creates the bucket. The gateway's one identity is the homeserver's key + pair, `WARP_PB_S3_ACCESS_KEY` and `WARP_PB_S3_SECRET`. +- **`docker-compose.pocketbase.seaweedfs.split.yml`**: master, volume, + filer, S3 gateway and admin UI (port 23646) as separate containers. + `seaweedfs-init` writes the homeserver's identity into the filer's IAM + store on every start, the store the admin UI writes too, so keys added + under *Object Store → Users* never displace it; the gateway does not + start until that identity is there. Set `SEAWEED_ADMIN_PASSWORD` as well. + +The S3 gateway and the admin UI are published on 127.0.0.1 only +(`SEAWEED_S3_BIND`, `SEAWEED_ADMIN_BIND`); master, volume and filer are not +published, since the volume server serves content by id with no +authentication. Both SeaweedFS files share a project and the +`seaweed-data` layout, so switching between them needs no migration. +`SEAWEED_IMAGE` pins the SeaweedFS release (4.45). + ## Prod files The `.prod.yml` files pull `$WARP_REGISTRY/warp`, `warp-admin` and diff --git a/Docker/docker-compose.pocketbase.s3.prod.yml b/Docker/docker-compose.pocketbase.s3.prod.yml new file mode 100644 index 0000000..94bd3bb --- /dev/null +++ b/Docker/docker-compose.pocketbase.s3.prod.yml @@ -0,0 +1,97 @@ +# Warp on PocketBase with media in an S3 bucket elsewhere: the homeserver, +# the admin panel and the web client, each in its own container, pulled +# from a registry. +# +# cd Docker +# cp .env.example .env # then edit it; set WARP_PB_S3_ENDPOINT, +# # WARP_PB_S3_ACCESS_KEY and WARP_PB_S3_SECRET +# docker compose -f docker-compose.pocketbase.s3.prod.yml up -d +# +# The database stays in /data/pb_data (warp-data); media goes to the bucket +# WARP_PB_S3_BUCKET (default warp) on WARP_PB_S3_ENDPOINT, which must already +# exist: nothing here runs a gateway or creates the bucket. For a gateway on +# this Docker host, use http://host.docker.internal:8333; the host entry below +# makes that name resolve. For a SeaweedFS that comes up with the stack, see +# docker-compose.pocketbase.seaweedfs.yml. 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. +# +# Create the first administrator once the server is up: +# +# docker compose -f docker-compose.pocketbase.s3.prod.yml exec warp warp create-account --username admin --admin +# +# Nothing is built here: the homeserver, admin panel and web client images +# come 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.s3.prod.yml pull && docker compose -f docker-compose.pocketbase.s3.prod.yml up -d + +name: warp-pocketbase-s3 + +services: + warp: + image: ${WARP_REGISTRY:-ghcr.io/dariuszjaniak}/warp:${WARP_VERSION:-latest} + pull_policy: always + restart: unless-stopped + extra_hosts: + # Lets WARP_PB_S3_ENDPOINT name the Docker host as host.docker.internal. + - "host.docker.internal:host-gateway" + # The homeserver does not check the endpoint at startup and would only + # fail on the first upload, so wait up to 30 seconds for it to answer at + # all (any HTTP status). The signing key is created on first start and + # kept in the volume: losing it means other servers no longer recognise + # this one. + entrypoint: ["/bin/sh", "-c"] + command: + - >- + i=0; until curl -s -o /dev/null --max-time 5 "$$WARP_PB_S3_ENDPOINT"; do + i=$$((i + 1)); if [ "$$i" -ge 30 ]; then echo "S3 endpoint $$WARP_PB_S3_ENDPOINT does not answer" >&2; exit 1; fi; + echo "waiting for S3 endpoint $$WARP_PB_S3_ENDPOINT" >&2; sleep 1; done; + test -f /data/signing.key || warp generate-keys --output /data/signing.key; exec warp serve + env_file: .env + environment: + WARP_DB_DRIVER: pocketbase + WARP_DB_CONNECTION_STRING: /data/pb_data + WARP_PB_S3_ENABLED: "true" + WARP_PB_S3_ENDPOINT: ${WARP_PB_S3_ENDPOINT:?set WARP_PB_S3_ENDPOINT in .env} + WARP_PB_S3_BUCKET: ${WARP_PB_S3_BUCKET:-warp} + # SeaweedFS and MinIO ignore the region; the client insists on one. + WARP_PB_S3_REGION: ${WARP_PB_S3_REGION:-us-east-1} + 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} + # true for SeaweedFS and MinIO, false for AWS S3 proper. + WARP_PB_S3_FORCE_PATH_STYLE: ${WARP_PB_S3_FORCE_PATH_STYLE:-true} + volumes: + - warp-data:/data + ports: + - "${WARP_PORT:-8008}:8008" + + admin: + image: ${WARP_REGISTRY:-ghcr.io/dariuszjaniak}/warp-admin:${WARP_VERSION:-latest} + pull_policy: always + restart: unless-stopped + depends_on: + - warp + env_file: .env + environment: + WARP_ADMIN_HOMESERVER: http://warp:8008 + ports: + - "${WARP_ADMIN_PORT:-8009}:8009" + + web: + image: ${WARP_REGISTRY:-ghcr.io/dariuszjaniak}/warp-web:${WARP_VERSION:-latest} + pull_policy: always + restart: unless-stopped + depends_on: + - warp + env_file: .env + environment: + WARP_WEB_HOMESERVER: http://warp:8008 + ports: + - "${WARP_WEB_PORT:-8080}:8080" + +volumes: + warp-data: diff --git a/Docker/docker-compose.pocketbase.s3.yml b/Docker/docker-compose.pocketbase.s3.yml new file mode 100644 index 0000000..cf34a6c --- /dev/null +++ b/Docker/docker-compose.pocketbase.s3.yml @@ -0,0 +1,96 @@ +# Warp on PocketBase with media in an S3 bucket elsewhere: the homeserver, +# the admin panel and the web client, each in its own container, built from +# this checkout. +# +# cd Docker +# cp .env.example .env # then edit it; set WARP_PB_S3_ENDPOINT, +# # WARP_PB_S3_ACCESS_KEY and WARP_PB_S3_SECRET +# docker compose -f docker-compose.pocketbase.s3.yml up -d --build +# +# The database stays in /data/pb_data (warp-data); media goes to the bucket +# WARP_PB_S3_BUCKET (default warp) on WARP_PB_S3_ENDPOINT, which must already +# exist: nothing here runs a gateway or creates the bucket. For a gateway on +# this Docker host, use http://host.docker.internal:8333; the host entry below +# makes that name resolve. For a SeaweedFS that comes up with the stack, see +# docker-compose.pocketbase.seaweedfs.yml. 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. +# +# Create the first administrator once the server is up: +# +# docker compose -f docker-compose.pocketbase.s3.yml exec warp warp create-account --username admin --admin + +name: warp-pocketbase-s3 + +services: + warp: + build: + context: .. + dockerfile: Dockerfile + image: warp:latest + restart: unless-stopped + extra_hosts: + # Lets WARP_PB_S3_ENDPOINT name the Docker host as host.docker.internal. + - "host.docker.internal:host-gateway" + # The homeserver does not check the endpoint at startup and would only + # fail on the first upload, so wait up to 30 seconds for it to answer at + # all (any HTTP status). The signing key is created on first start and + # kept in the volume: losing it means other servers no longer recognise + # this one. + entrypoint: ["/bin/sh", "-c"] + command: + - >- + i=0; until curl -s -o /dev/null --max-time 5 "$$WARP_PB_S3_ENDPOINT"; do + i=$$((i + 1)); if [ "$$i" -ge 30 ]; then echo "S3 endpoint $$WARP_PB_S3_ENDPOINT does not answer" >&2; exit 1; fi; + echo "waiting for S3 endpoint $$WARP_PB_S3_ENDPOINT" >&2; sleep 1; done; + test -f /data/signing.key || warp generate-keys --output /data/signing.key; exec warp serve + env_file: .env + environment: + WARP_DB_DRIVER: pocketbase + WARP_DB_CONNECTION_STRING: /data/pb_data + WARP_PB_S3_ENABLED: "true" + WARP_PB_S3_ENDPOINT: ${WARP_PB_S3_ENDPOINT:?set WARP_PB_S3_ENDPOINT in .env} + WARP_PB_S3_BUCKET: ${WARP_PB_S3_BUCKET:-warp} + # SeaweedFS and MinIO ignore the region; the client insists on one. + WARP_PB_S3_REGION: ${WARP_PB_S3_REGION:-us-east-1} + 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} + # true for SeaweedFS and MinIO, false for AWS S3 proper. + WARP_PB_S3_FORCE_PATH_STYLE: ${WARP_PB_S3_FORCE_PATH_STYLE:-true} + volumes: + - warp-data:/data + ports: + - "${WARP_PORT:-8008}:8008" + + admin: + build: + context: .. + dockerfile: apps/admin/Dockerfile + image: warp-admin:latest + restart: unless-stopped + depends_on: + - warp + env_file: .env + environment: + WARP_ADMIN_HOMESERVER: http://warp:8008 + ports: + - "${WARP_ADMIN_PORT:-8009}:8009" + + web: + build: + context: .. + dockerfile: apps/web/Dockerfile + image: warp-web:latest + restart: unless-stopped + depends_on: + - warp + env_file: .env + environment: + WARP_WEB_HOMESERVER: http://warp:8008 + ports: + - "${WARP_WEB_PORT:-8080}:8080" + +volumes: + warp-data: diff --git a/Docker/docker-compose.pocketbase.seaweedfs.prod.yml b/Docker/docker-compose.pocketbase.seaweedfs.prod.yml new file mode 100644 index 0000000..5cc7b35 --- /dev/null +++ b/Docker/docker-compose.pocketbase.seaweedfs.prod.yml @@ -0,0 +1,134 @@ +# Warp on PocketBase with media in SeaweedFS: the homeserver, the admin panel +# and the web client pulled from a registry, plus SeaweedFS as one +# container (`weed server -s3`: master, volume, filer and S3 gateway in one +# process). +# +# cd Docker +# 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 (default warp) in SeaweedFS (seaweed-data), whose gateway +# has exactly one identity: the homeserver's. For SeaweedFS's admin UI and +# per-role containers, see docker-compose.pocketbase.seaweedfs.split.yml. +# 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 file shares its project and volumes with +# docker-compose.pocketbase.seaweedfs.split.yml: SeaweedFS's data has the +# same layout either way, so switching between the two 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 homeserver, admin panel and web client images +# come from WARP_REGISTRY (default ghcr.io/dariuszjaniak) at tag +# WARP_VERSION (default latest), and SeaweedFS from SEAWEED_IMAGE. 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-pocketbase-seaweedfs + +services: + seaweedfs: + image: ${SEAWEED_IMAGE:-chrislusf/seaweedfs:4.45} + restart: unless-stopped + command: server -dir=/data -s3 -volume.max=0 -master.volumeSizeLimitMB=1024 + environment: + # With no -s3.config, the gateway takes its single identity from these, + # which is also what takes it out of its allow-anyone default. The same + # pair the homeserver signs with. + AWS_ACCESS_KEY_ID: ${WARP_PB_S3_ACCESS_KEY:?set WARP_PB_S3_ACCESS_KEY in .env} + AWS_SECRET_ACCESS_KEY: ${WARP_PB_S3_SECRET:?set WARP_PB_S3_SECRET in .env} + volumes: + - seaweed-data:/data + ports: + # For aws-cli and the like; the homeserver uses the compose network. + - "${SEAWEED_S3_BIND:-127.0.0.1}:${SEAWEED_S3_PORT:-8333}:8333" + healthcheck: + # The gateway answering means master, volume and filer are up too. + test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8333/healthz >/dev/null || exit 1"] + interval: 10s + timeout: 3s + retries: 12 + start_period: 20s + + # Runs once per start and exits. The homeserver never creates its bucket; + # creating one that exists is a no-op. + seaweedfs-init: + image: ${SEAWEED_IMAGE:-chrislusf/seaweedfs:4.45} + restart: "no" + depends_on: + seaweedfs: + condition: service_healthy + environment: + WARP_PB_S3_BUCKET: ${WARP_PB_S3_BUCKET:-warp} + entrypoint: ["/bin/sh", "-c"] + command: + - echo "s3.bucket.create -name $$WARP_PB_S3_BUCKET" | weed shell -master=seaweedfs:9333 >/dev/null + + warp: + image: ${WARP_REGISTRY:-ghcr.io/dariuszjaniak}/warp:${WARP_VERSION:-latest} + pull_policy: always + restart: unless-stopped + depends_on: + seaweedfs: + condition: service_healthy + seaweedfs-init: + condition: service_completed_successfully + # The signing key is created on first start and kept in the volume: + # losing it means other servers no longer recognise this one. + entrypoint: ["/bin/sh", "-c"] + command: + - test -f /data/signing.key || warp generate-keys --output /data/signing.key; exec warp serve + env_file: .env + environment: + WARP_DB_DRIVER: pocketbase + WARP_DB_CONNECTION_STRING: /data/pb_data + # The gateway on the compose network; these override whatever .env + # says about S3. + WARP_PB_S3_ENABLED: "true" + WARP_PB_S3_ENDPOINT: http://seaweedfs: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" + 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 + ports: + - "${WARP_PORT:-8008}:8008" + + admin: + image: ${WARP_REGISTRY:-ghcr.io/dariuszjaniak}/warp-admin:${WARP_VERSION:-latest} + pull_policy: always + restart: unless-stopped + depends_on: + - warp + env_file: .env + environment: + WARP_ADMIN_HOMESERVER: http://warp:8008 + ports: + - "${WARP_ADMIN_PORT:-8009}:8009" + + web: + image: ${WARP_REGISTRY:-ghcr.io/dariuszjaniak}/warp-web:${WARP_VERSION:-latest} + pull_policy: always + restart: unless-stopped + depends_on: + - warp + env_file: .env + environment: + WARP_WEB_HOMESERVER: http://warp:8008 + ports: + - "${WARP_WEB_PORT:-8080}:8080" + +volumes: + warp-data: + seaweed-data: diff --git a/Docker/docker-compose.pocketbase.seaweedfs.split.prod.yml b/Docker/docker-compose.pocketbase.seaweedfs.split.prod.yml new file mode 100644 index 0000000..b4229a8 --- /dev/null +++ b/Docker/docker-compose.pocketbase.seaweedfs.split.prod.yml @@ -0,0 +1,240 @@ +# Warp on PocketBase with media in SeaweedFS split into its roles: the +# homeserver, the admin panel and the web client pulled from a registry, +# plus SeaweedFS's master, volume, filer, S3 gateway and admin UI as +# separate containers. +# +# cd Docker +# 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 (default warp) in SeaweedFS (seaweed-data). Over +# docker-compose.pocketbase.seaweedfs.yml this buys SeaweedFS's admin UI, +# where S3 identities are created and revoked under Object Store → Users, +# per-role restarts, upgrades and metrics, and room for a second volume +# server. The S3 behaviour is the same. 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. +# Master, volume and filer are not published: the volume server serves file +# content by id with no authentication at all. +# +# Master, volume and filer share seaweed-data in the layout `weed server +# -dir` writes, and this file shares its project with +# docker-compose.pocketbase.seaweedfs.yml, so switching between the two +# 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 homeserver, admin panel and web client images +# come from WARP_REGISTRY (default ghcr.io/dariuszjaniak) at tag +# WARP_VERSION (default latest), and SeaweedFS from SEAWEED_IMAGE. 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-pocketbase-seaweedfs + +services: + # Volume and topology metadata; hands out file ids. -ip is the name the + # other roles reach it by. + seaweedfs-master: + image: ${SEAWEED_IMAGE:-chrislusf/seaweedfs:4.45} + restart: unless-stopped + command: > + master -ip=seaweedfs-master -ip.bind=0.0.0.0 -mdir=/data + -volumeSizeLimitMB=1024 -metricsPort=9324 + volumes: + - seaweed-data:/data + healthcheck: + test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:9333/cluster/status >/dev/null || exit 1"] + interval: 10s + timeout: 3s + retries: 12 + start_period: 15s + + # Where the bytes land. -max=0 sizes itself from free disk instead of the + # default cap of 8 volumes. + seaweedfs-volume: + image: ${SEAWEED_IMAGE:-chrislusf/seaweedfs:4.45} + restart: unless-stopped + command: > + volume -master=seaweedfs-master:9333 -ip=seaweedfs-volume -ip.bind=0.0.0.0 + -port=8080 -dir=/data -max=0 -metricsPort=9325 + depends_on: + seaweedfs-master: + condition: service_healthy + volumes: + - seaweed-data:/data + healthcheck: + test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8080/healthz >/dev/null || exit 1"] + interval: 10s + timeout: 3s + retries: 12 + start_period: 15s + + # The directory tree over the volume store (buckets, object keys) and the + # IAM store the S3 identities live in, kept on the data volume. + seaweedfs-filer: + image: ${SEAWEED_IMAGE:-chrislusf/seaweedfs:4.45} + restart: unless-stopped + command: > + filer -master=seaweedfs-master:9333 -ip=seaweedfs-filer -ip.bind=0.0.0.0 + -port=8888 -defaultStoreDir=/data -metricsPort=9326 + depends_on: + seaweedfs-volume: + condition: service_healthy + volumes: + - seaweed-data:/data + healthcheck: + test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8888/healthz >/dev/null || exit 1"] + interval: 10s + timeout: 3s + retries: 12 + start_period: 15s + + # Runs once per start and exits: creates the bucket (the homeserver never + # does) and writes the homeserver's identity into the filer's IAM store, + # both in place, so a rotated WARP_PB_S3_SECRET lands on the next start. + # That store is the one the admin UI writes, so the gateway gets no AWS_* + # variables or -config: those lower-priority sources would be displaced by + # the first identity added in the admin UI. The closing grep fails the + # start if the identity is missing, rather than letting the gateway come + # up open to anyone. + seaweedfs-init: + image: ${SEAWEED_IMAGE:-chrislusf/seaweedfs:4.45} + restart: "no" + depends_on: + seaweedfs-filer: + condition: service_healthy + environment: + # Expanded by the shell inside the container, so the secret stays off + # the command line. + WARP_PB_S3_BUCKET: ${WARP_PB_S3_BUCKET:-warp} + 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} + entrypoint: ["/bin/sh", "-c"] + command: + - | + set -e + printf '%s\n' \ + "s3.bucket.create -name $$WARP_PB_S3_BUCKET" \ + "s3.configure -user warp -access_key $$WARP_PB_S3_ACCESS_KEY -secret_key $$WARP_PB_S3_SECRET -actions Admin -apply" \ + | weed shell -master=seaweedfs-master:9333 -filer=seaweedfs-filer:8888 >/dev/null + echo "s3.configure" \ + | weed shell -master=seaweedfs-master:9333 -filer=seaweedfs-filer:8888 \ + | grep -q "$$WARP_PB_S3_ACCESS_KEY" + + # The homeserver's endpoint. Credentials come from the filer's IAM store. + seaweedfs-s3: + image: ${SEAWEED_IMAGE:-chrislusf/seaweedfs:4.45} + restart: unless-stopped + command: s3 -filer=seaweedfs-filer:8888 -ip.bind=0.0.0.0 -port=8333 -metricsPort=9327 + depends_on: + seaweedfs-filer: + condition: service_healthy + seaweedfs-init: + condition: service_completed_successfully + ports: + # For aws-cli and the like; the homeserver uses the compose network. + - "${SEAWEED_S3_BIND:-127.0.0.1}:${SEAWEED_S3_PORT:-8333}:8333" + healthcheck: + test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8333/healthz >/dev/null || exit 1"] + interval: 10s + timeout: 3s + retries: 12 + start_period: 15s + + # Cluster view, buckets, maintenance, and Object Store → Users. It can mint + # credentials for the bucket, and weed serves it with no authentication + # when the password is empty, so the password is required. Read from + # WEED_ADMIN_* to keep it off the command line. + seaweedfs-admin: + image: ${SEAWEED_IMAGE:-chrislusf/seaweedfs:4.45} + restart: unless-stopped + command: admin -port=23646 -master=seaweedfs-master:9333 -dataDir=/data -metricsPort=9328 + depends_on: + seaweedfs-master: + condition: service_healthy + environment: + 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: + - seaweed-admin:/data + ports: + - "${SEAWEED_ADMIN_BIND:-127.0.0.1}:${SEAWEED_ADMIN_PORT:-23646}:23646" + healthcheck: + test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:23646/health >/dev/null || exit 1"] + interval: 10s + timeout: 3s + retries: 12 + start_period: 15s + + warp: + image: ${WARP_REGISTRY:-ghcr.io/dariuszjaniak}/warp:${WARP_VERSION:-latest} + pull_policy: always + restart: unless-stopped + depends_on: + seaweedfs-s3: + condition: service_healthy + # The signing key is created on first start and kept in the volume: + # losing it means other servers no longer recognise this one. + entrypoint: ["/bin/sh", "-c"] + command: + - test -f /data/signing.key || warp generate-keys --output /data/signing.key; exec warp serve + env_file: .env + environment: + WARP_DB_DRIVER: pocketbase + WARP_DB_CONNECTION_STRING: /data/pb_data + # The gateway on the compose network; these override whatever .env + # says about S3. + WARP_PB_S3_ENABLED: "true" + WARP_PB_S3_ENDPOINT: http://seaweedfs-s3: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" + 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 + ports: + - "${WARP_PORT:-8008}:8008" + + admin: + image: ${WARP_REGISTRY:-ghcr.io/dariuszjaniak}/warp-admin:${WARP_VERSION:-latest} + pull_policy: always + restart: unless-stopped + depends_on: + - warp + env_file: .env + environment: + WARP_ADMIN_HOMESERVER: http://warp:8008 + ports: + - "${WARP_ADMIN_PORT:-8009}:8009" + + web: + image: ${WARP_REGISTRY:-ghcr.io/dariuszjaniak}/warp-web:${WARP_VERSION:-latest} + pull_policy: always + restart: unless-stopped + depends_on: + - warp + env_file: .env + environment: + WARP_WEB_HOMESERVER: http://warp:8008 + ports: + - "${WARP_WEB_PORT:-8080}:8080" + +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/docker-compose.pocketbase.seaweedfs.split.yml b/Docker/docker-compose.pocketbase.seaweedfs.split.yml new file mode 100644 index 0000000..10b4fa8 --- /dev/null +++ b/Docker/docker-compose.pocketbase.seaweedfs.split.yml @@ -0,0 +1,239 @@ +# Warp on PocketBase with media in SeaweedFS split into its roles: the +# homeserver, the admin panel and the web client built from this checkout, +# plus SeaweedFS's master, volume, filer, S3 gateway and admin UI as +# separate containers. +# +# cd Docker +# 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 (default warp) in SeaweedFS (seaweed-data). Over +# docker-compose.pocketbase.seaweedfs.yml this buys SeaweedFS's admin UI, +# where S3 identities are created and revoked under Object Store → Users, +# per-role restarts, upgrades and metrics, and room for a second volume +# server. The S3 behaviour is the same. 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. +# Master, volume and filer are not published: the volume server serves file +# content by id with no authentication at all. +# +# Master, volume and filer share seaweed-data in the layout `weed server +# -dir` writes, and this file shares its project with +# docker-compose.pocketbase.seaweedfs.yml, so switching between the two +# 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-pocketbase-seaweedfs + +services: + # Volume and topology metadata; hands out file ids. -ip is the name the + # other roles reach it by. + seaweedfs-master: + image: ${SEAWEED_IMAGE:-chrislusf/seaweedfs:4.45} + restart: unless-stopped + command: > + master -ip=seaweedfs-master -ip.bind=0.0.0.0 -mdir=/data + -volumeSizeLimitMB=1024 -metricsPort=9324 + volumes: + - seaweed-data:/data + healthcheck: + test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:9333/cluster/status >/dev/null || exit 1"] + interval: 10s + timeout: 3s + retries: 12 + start_period: 15s + + # Where the bytes land. -max=0 sizes itself from free disk instead of the + # default cap of 8 volumes. + seaweedfs-volume: + image: ${SEAWEED_IMAGE:-chrislusf/seaweedfs:4.45} + restart: unless-stopped + command: > + volume -master=seaweedfs-master:9333 -ip=seaweedfs-volume -ip.bind=0.0.0.0 + -port=8080 -dir=/data -max=0 -metricsPort=9325 + depends_on: + seaweedfs-master: + condition: service_healthy + volumes: + - seaweed-data:/data + healthcheck: + test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8080/healthz >/dev/null || exit 1"] + interval: 10s + timeout: 3s + retries: 12 + start_period: 15s + + # The directory tree over the volume store (buckets, object keys) and the + # IAM store the S3 identities live in, kept on the data volume. + seaweedfs-filer: + image: ${SEAWEED_IMAGE:-chrislusf/seaweedfs:4.45} + restart: unless-stopped + command: > + filer -master=seaweedfs-master:9333 -ip=seaweedfs-filer -ip.bind=0.0.0.0 + -port=8888 -defaultStoreDir=/data -metricsPort=9326 + depends_on: + seaweedfs-volume: + condition: service_healthy + volumes: + - seaweed-data:/data + healthcheck: + test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8888/healthz >/dev/null || exit 1"] + interval: 10s + timeout: 3s + retries: 12 + start_period: 15s + + # Runs once per start and exits: creates the bucket (the homeserver never + # does) and writes the homeserver's identity into the filer's IAM store, + # both in place, so a rotated WARP_PB_S3_SECRET lands on the next start. + # That store is the one the admin UI writes, so the gateway gets no AWS_* + # variables or -config: those lower-priority sources would be displaced by + # the first identity added in the admin UI. The closing grep fails the + # start if the identity is missing, rather than letting the gateway come + # up open to anyone. + seaweedfs-init: + image: ${SEAWEED_IMAGE:-chrislusf/seaweedfs:4.45} + restart: "no" + depends_on: + seaweedfs-filer: + condition: service_healthy + environment: + # Expanded by the shell inside the container, so the secret stays off + # the command line. + WARP_PB_S3_BUCKET: ${WARP_PB_S3_BUCKET:-warp} + 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} + entrypoint: ["/bin/sh", "-c"] + command: + - | + set -e + printf '%s\n' \ + "s3.bucket.create -name $$WARP_PB_S3_BUCKET" \ + "s3.configure -user warp -access_key $$WARP_PB_S3_ACCESS_KEY -secret_key $$WARP_PB_S3_SECRET -actions Admin -apply" \ + | weed shell -master=seaweedfs-master:9333 -filer=seaweedfs-filer:8888 >/dev/null + echo "s3.configure" \ + | weed shell -master=seaweedfs-master:9333 -filer=seaweedfs-filer:8888 \ + | grep -q "$$WARP_PB_S3_ACCESS_KEY" + + # The homeserver's endpoint. Credentials come from the filer's IAM store. + seaweedfs-s3: + image: ${SEAWEED_IMAGE:-chrislusf/seaweedfs:4.45} + restart: unless-stopped + command: s3 -filer=seaweedfs-filer:8888 -ip.bind=0.0.0.0 -port=8333 -metricsPort=9327 + depends_on: + seaweedfs-filer: + condition: service_healthy + seaweedfs-init: + condition: service_completed_successfully + ports: + # For aws-cli and the like; the homeserver uses the compose network. + - "${SEAWEED_S3_BIND:-127.0.0.1}:${SEAWEED_S3_PORT:-8333}:8333" + healthcheck: + test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8333/healthz >/dev/null || exit 1"] + interval: 10s + timeout: 3s + retries: 12 + start_period: 15s + + # Cluster view, buckets, maintenance, and Object Store → Users. It can mint + # credentials for the bucket, and weed serves it with no authentication + # when the password is empty, so the password is required. Read from + # WEED_ADMIN_* to keep it off the command line. + seaweedfs-admin: + image: ${SEAWEED_IMAGE:-chrislusf/seaweedfs:4.45} + restart: unless-stopped + command: admin -port=23646 -master=seaweedfs-master:9333 -dataDir=/data -metricsPort=9328 + depends_on: + seaweedfs-master: + condition: service_healthy + environment: + 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: + - seaweed-admin:/data + ports: + - "${SEAWEED_ADMIN_BIND:-127.0.0.1}:${SEAWEED_ADMIN_PORT:-23646}:23646" + healthcheck: + test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:23646/health >/dev/null || exit 1"] + interval: 10s + timeout: 3s + retries: 12 + start_period: 15s + + warp: + build: + context: .. + dockerfile: Dockerfile + image: warp:latest + restart: unless-stopped + depends_on: + seaweedfs-s3: + condition: service_healthy + # The signing key is created on first start and kept in the volume: + # losing it means other servers no longer recognise this one. + entrypoint: ["/bin/sh", "-c"] + command: + - test -f /data/signing.key || warp generate-keys --output /data/signing.key; exec warp serve + env_file: .env + environment: + WARP_DB_DRIVER: pocketbase + WARP_DB_CONNECTION_STRING: /data/pb_data + # The gateway on the compose network; these override whatever .env + # says about S3. + WARP_PB_S3_ENABLED: "true" + WARP_PB_S3_ENDPOINT: http://seaweedfs-s3: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" + 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 + ports: + - "${WARP_PORT:-8008}:8008" + + admin: + build: + context: .. + dockerfile: apps/admin/Dockerfile + image: warp-admin:latest + restart: unless-stopped + depends_on: + - warp + env_file: .env + environment: + WARP_ADMIN_HOMESERVER: http://warp:8008 + ports: + - "${WARP_ADMIN_PORT:-8009}:8009" + + web: + build: + context: .. + dockerfile: apps/web/Dockerfile + image: warp-web:latest + restart: unless-stopped + depends_on: + - warp + env_file: .env + environment: + WARP_WEB_HOMESERVER: http://warp:8008 + ports: + - "${WARP_WEB_PORT:-8080}:8080" + +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/docker-compose.pocketbase.seaweedfs.yml b/Docker/docker-compose.pocketbase.seaweedfs.yml new file mode 100644 index 0000000..f5968a4 --- /dev/null +++ b/Docker/docker-compose.pocketbase.seaweedfs.yml @@ -0,0 +1,133 @@ +# Warp on PocketBase with media in SeaweedFS: the homeserver, the admin panel +# and the web client built from this checkout, plus SeaweedFS as one +# container (`weed server -s3`: master, volume, filer and S3 gateway in one +# process). +# +# cd Docker +# 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 (default warp) in SeaweedFS (seaweed-data), whose gateway +# has exactly one identity: the homeserver's. For SeaweedFS's admin UI and +# per-role containers, see docker-compose.pocketbase.seaweedfs.split.yml. +# 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 file shares its project and volumes with +# docker-compose.pocketbase.seaweedfs.split.yml: SeaweedFS's data has the +# same layout either way, so switching between the two 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-pocketbase-seaweedfs + +services: + seaweedfs: + image: ${SEAWEED_IMAGE:-chrislusf/seaweedfs:4.45} + restart: unless-stopped + command: server -dir=/data -s3 -volume.max=0 -master.volumeSizeLimitMB=1024 + environment: + # With no -s3.config, the gateway takes its single identity from these, + # which is also what takes it out of its allow-anyone default. The same + # pair the homeserver signs with. + AWS_ACCESS_KEY_ID: ${WARP_PB_S3_ACCESS_KEY:?set WARP_PB_S3_ACCESS_KEY in .env} + AWS_SECRET_ACCESS_KEY: ${WARP_PB_S3_SECRET:?set WARP_PB_S3_SECRET in .env} + volumes: + - seaweed-data:/data + ports: + # For aws-cli and the like; the homeserver uses the compose network. + - "${SEAWEED_S3_BIND:-127.0.0.1}:${SEAWEED_S3_PORT:-8333}:8333" + healthcheck: + # The gateway answering means master, volume and filer are up too. + test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8333/healthz >/dev/null || exit 1"] + interval: 10s + timeout: 3s + retries: 12 + start_period: 20s + + # Runs once per start and exits. The homeserver never creates its bucket; + # creating one that exists is a no-op. + seaweedfs-init: + image: ${SEAWEED_IMAGE:-chrislusf/seaweedfs:4.45} + restart: "no" + depends_on: + seaweedfs: + condition: service_healthy + environment: + WARP_PB_S3_BUCKET: ${WARP_PB_S3_BUCKET:-warp} + entrypoint: ["/bin/sh", "-c"] + command: + - echo "s3.bucket.create -name $$WARP_PB_S3_BUCKET" | weed shell -master=seaweedfs:9333 >/dev/null + + warp: + build: + context: .. + dockerfile: Dockerfile + image: warp:latest + restart: unless-stopped + depends_on: + seaweedfs: + condition: service_healthy + seaweedfs-init: + condition: service_completed_successfully + # The signing key is created on first start and kept in the volume: + # losing it means other servers no longer recognise this one. + entrypoint: ["/bin/sh", "-c"] + command: + - test -f /data/signing.key || warp generate-keys --output /data/signing.key; exec warp serve + env_file: .env + environment: + WARP_DB_DRIVER: pocketbase + WARP_DB_CONNECTION_STRING: /data/pb_data + # The gateway on the compose network; these override whatever .env + # says about S3. + WARP_PB_S3_ENABLED: "true" + WARP_PB_S3_ENDPOINT: http://seaweedfs: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" + 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 + ports: + - "${WARP_PORT:-8008}:8008" + + admin: + build: + context: .. + dockerfile: apps/admin/Dockerfile + image: warp-admin:latest + restart: unless-stopped + depends_on: + - warp + env_file: .env + environment: + WARP_ADMIN_HOMESERVER: http://warp:8008 + ports: + - "${WARP_ADMIN_PORT:-8009}:8009" + + web: + build: + context: .. + dockerfile: apps/web/Dockerfile + image: warp-web:latest + restart: unless-stopped + depends_on: + - warp + env_file: .env + environment: + WARP_WEB_HOMESERVER: http://warp:8008 + ports: + - "${WARP_WEB_PORT:-8080}:8080" + +volumes: + warp-data: + seaweed-data: