Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,12 @@ PAPERLESS_DB_PASSWORD=paperless
PAPERLESS_SECRET_KEY=CHANGE_ME_IN_PRODUCTION

# ----------------------------------
# Object Storage (MinIO / S3) — carrier label files
# Object Storage (S3-compatible; Garage in the dev stack) — labels and product images
# ----------------------------------
MINIO_ROOT_USER=opentaberna
MINIO_ROOT_PASSWORD=opentaberna_secret
# The dev stack's Garage creates its access key from STORAGE_ACCESS_KEY and
# STORAGE_SECRET_KEY below. It also needs a cluster secret of 32 random bytes:
# openssl rand -hex 32
GARAGE_RPC_SECRET=
STORAGE_ENDPOINT_URL=http://localhost:9000
STORAGE_ACCESS_KEY=opentaberna
STORAGE_SECRET_KEY=opentaberna_secret
Expand Down
15 changes: 8 additions & 7 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,34 +63,35 @@ jobs:
KEYCLOAK_CLIENT_SECRET=opentaberna-secret
STRIPE_SECRET_KEY=sk_test_placeholder
STRIPE_WEBHOOK_SECRET=whsec_placeholder
GARAGE_RPC_SECRET=$(openssl rand -hex 32)
LOG_LEVEL=${{ github.event.inputs.log_level || 'INFO' }}
EOF
echo "✅ Test environment file created"

- name: 🔧 Install system tools
run: sudo apt-get install -y postgresql-client redis-tools

- name: 🐳 Start services (DB, Redis, Keycloak, MinIO)
- name: 🐳 Start services (DB, Redis, Keycloak, Garage)
run: |
mkdir -p postgres_data redis_data minio_data
docker compose -f docker-compose.dev.yml up opentaberna-db opentaberna-redis opentaberna-keycloak opentaberna-minio -d
mkdir -p postgres_data redis_data garage_data
docker compose -f docker-compose.dev.yml up opentaberna-db opentaberna-redis opentaberna-keycloak opentaberna-garage -d
echo "✅ Services started"

- name: 🗄️ Wait for backing services
run: |
echo "⏳ Waiting for DB, Redis, Keycloak and MinIO..."
echo "⏳ Waiting for DB, Redis, Keycloak and Garage..."

for i in {1..40}; do
echo "Attempt $i/40..."

pg_isready -h localhost -p 5432 -U opentaberna 2>/dev/null && PG_READY=1 && echo "✅ Postgres ready"
redis-cli -h localhost -p 6379 ping 2>/dev/null | grep -q PONG && REDIS_READY=1 && echo "✅ Redis ready"
curl -sf http://localhost:8080/realms/opentaberna/.well-known/openid-configuration > /dev/null 2>&1 && KC_READY=1 && echo "✅ Keycloak ready"
# MinIO backs the product-image endpoints; without it those tests
# Garage backs the product-image endpoints; without it those tests
# fail with a storage error rather than anything meaningful.
curl -sf http://localhost:9000/minio/health/live > /dev/null 2>&1 && MINIO_READY=1 && echo "✅ MinIO ready"
curl -sf http://localhost:3903/health > /dev/null 2>&1 && GARAGE_READY=1 && echo "✅ Garage ready"

[ "$PG_READY" = "1" ] && [ "$REDIS_READY" = "1" ] && [ "$KC_READY" = "1" ] && [ "$MINIO_READY" = "1" ] && echo "✅ All backing services ready!" && exit 0
[ "$PG_READY" = "1" ] && [ "$REDIS_READY" = "1" ] && [ "$KC_READY" = "1" ] && [ "$GARAGE_READY" = "1" ] && echo "✅ All backing services ready!" && exit 0

sleep 5
done
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -146,4 +146,4 @@ cython_debug/
# postgresql db directory
postgres_data/
redis_data/
minio_data/
garage_data/
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ The development stack also starts a Stripe CLI listener. Set a Stripe test-mode
the API and provides its generated webhook signing secret automatically. No
manual `stripe listen` process or `STRIPE_WEBHOOK_SECRET` copy is required.

Object storage is [Garage](https://garagehq.deuxfleurs.fr), an S3-compatible store
(MinIO's images are no longer published). It needs `GARAGE_RPC_SECRET` in `.env` —
generate one with `openssl rand -hex 32`. On start it creates its access key from
`STORAGE_ACCESS_KEY`/`STORAGE_SECRET_KEY`; the API creates its buckets itself. The S3
API is on `http://localhost:9000`, its health check on `http://localhost:3903/health`.

# Pipelines

This FastAPI can be build and tested via GitHub workflows. There are two available workflows:
Expand Down
35 changes: 20 additions & 15 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ services:
REDIS_URL: redis://opentaberna-redis:6379/0
KEYCLOAK_URL: http://opentaberna-keycloak:8080
KEYCLOAK_PUBLIC_URL: http://localhost:8080
STORAGE_ENDPOINT_URL: http://opentaberna-minio:9000
STORAGE_ENDPOINT_URL: http://opentaberna-garage:3900
OTEL_EXPORTER_OTLP_ENDPOINT: http://opentaberna-otel-collector:4318
OTEL_SERVICE_NAME: opentaberna-api
PAPERLESS_URL: http://opentaberna-paperless:8000
Expand All @@ -33,7 +33,7 @@ services:
condition: service_started
opentaberna-keycloak:
condition: service_started
opentaberna-minio:
opentaberna-garage:
condition: service_started
opentaberna-stripe-listener:
condition: service_started
Expand Down Expand Up @@ -153,25 +153,30 @@ services:
retries: 10
start_period: 60s

opentaberna-minio:
image: minio/minio:latest
command: server /data --console-address ":9001"
opentaberna-garage:
image: dxflrs/garage:v2.4.1
# --single-node lays out a one-node cluster and --default-access-key creates
# the API's key on start, so the store needs no manual setup. The API
# creates its buckets itself.
command: /garage server --single-node --default-access-key
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-opentaberna}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-opentaberna_secret}
GARAGE_RPC_SECRET: ${GARAGE_RPC_SECRET:?set GARAGE_RPC_SECRET in .env, generate one with openssl rand -hex 32}
GARAGE_DEFAULT_ACCESS_KEY: ${STORAGE_ACCESS_KEY:-opentaberna}
GARAGE_DEFAULT_SECRET_KEY: ${STORAGE_SECRET_KEY:-opentaberna_secret}
volumes:
- ./minio_data:/data
- ./docker/garage/garage.toml:/etc/garage.toml:ro
- ./garage_data:/var/lib/garage
ports:
- "9000:9000" # S3 API
- "9001:9001" # MinIO Web Console
- "9000:3900" # S3 API
- "3903:3903" # Admin API (GET /health)
restart: unless-stopped
container_name: opentaberna-minio
container_name: opentaberna-garage
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:9000/minio/health/live || exit 1"]
test: ["CMD", "/garage", "status"]
interval: 30s
timeout: 10s
retries: 5
start_period: 20s
start_period: 10s

opentaberna-worker:
build:
Expand All @@ -182,7 +187,7 @@ services:
env_file: .env
environment:
REDIS_URL: redis://opentaberna-redis:6379/0
STORAGE_ENDPOINT_URL: http://opentaberna-minio:9000
STORAGE_ENDPOINT_URL: http://opentaberna-garage:3900
OTEL_EXPORTER_OTLP_ENDPOINT: http://opentaberna-otel-collector:4318
OTEL_SERVICE_NAME: opentaberna-worker
restart: unless-stopped
Expand All @@ -198,7 +203,7 @@ services:
condition: service_healthy
opentaberna-redis:
condition: service_healthy
opentaberna-minio:
opentaberna-garage:
condition: service_healthy

opentaberna-mail:
Expand Down
18 changes: 18 additions & 0 deletions docker/garage/garage.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Single-node Garage for local development and CI. Not a production config:
# one copy of every object, no redundancy.
metadata_dir = "/var/lib/garage/meta"
data_dir = "/var/lib/garage/data"
db_engine = "sqlite"
replication_factor = 1

rpc_bind_addr = "[::]:3901"
rpc_public_addr = "127.0.0.1:3901"

[s3_api]
# The API's boto client signs for us-east-1 by default; Garage rejects
# requests signed for any region other than this one.
s3_region = "us-east-1"
api_bind_addr = "[::]:3900"

[admin]
api_bind_addr = "[::]:3903"
10 changes: 5 additions & 5 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,17 +201,17 @@ could otherwise drive the back office.
| `smtp_password` | str | Empty | SMTP password |
| `email_from` | str | `noreply@opentaberna.local` | Envelope sender address |

### Object Storage (MinIO / S3)
### Object Storage (S3-compatible)

| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `storage_endpoint_url` | str | `http://localhost:9000` | S3-compatible endpoint |
| `storage_access_key` | str | `minioadmin` | Access key |
| `storage_secret_key` | str | `minioadmin` | Secret key |
| `storage_bucket_labels` | str | `labels` | Bucket holding carrier labels |
| `storage_access_key` | str | `opentaberna` | Access key |
| `storage_secret_key` | str | `opentaberna_secret` | Secret key |
| `storage_bucket_labels` | str | `shipping-labels` | Bucket holding carrier labels |
| `storage_bucket_items` | str | `item-images` | Bucket holding product images |
| `storage_max_image_bytes` | int | `5242880` | Largest product image accepted (5 MB) |
| `storage_region` | str | `us-east-1` | Region name |
| `storage_region` | str | `us-east-1` | Region name; must match the store's region (`s3_region` in `docker/garage/garage.toml`) |

### DHL (Carrier Adapter)

Expand Down
10 changes: 5 additions & 5 deletions src/app/shared/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,18 +248,18 @@ class Settings(BaseSettings):
description="How long (in minutes) a stock reservation is held before it expires",
)

# MinIO / S3 object storage
# S3-compatible object storage (Garage in the dev stack)
storage_endpoint_url: str = Field(
default="http://localhost:9000",
description="S3-compatible storage endpoint URL (MinIO or AWS S3)",
description="S3-compatible storage endpoint URL",
)
storage_access_key: str = Field(
default="opentaberna",
description="Storage access key ID (MinIO root user or AWS access key)",
description="Storage access key ID",
)
storage_secret_key: str = Field(
default="opentaberna_secret",
description="Storage secret access key (MinIO root password or AWS secret key)",
description="Storage secret access key",
)
storage_bucket_items: str = Field(
default="item-images",
Expand All @@ -278,7 +278,7 @@ class Settings(BaseSettings):
)
storage_region: str = Field(
default="us-east-1",
description="Storage region (MinIO ignores this; required by boto3 client)",
description="Storage region; must match the store's configured region",
)

# DHL Parcel DE REST API
Expand Down
Loading