Prepare QSfera production release

This commit is contained in:
Курнат Андрей
2026-06-08 19:57:40 +03:00
parent 2315f25754
commit 43caef7a6a
27 changed files with 1159 additions and 42 deletions
@@ -15,4 +15,6 @@ This deployment example is documented in two locations for different audiences:
Providing details which are more developer focused. This description can also be used when deviating from the default.\
Note that this examples uses self signed certificates and is intended for testing purposes.
For production rollouts from this compose directory, use `env.production.example` as the starting point for `.env`.
It keeps TLS validation enabled, disables demo users, and requires explicit admin, Keycloak, and S3 secrets instead of demo defaults.
Run `./validate-production-env.sh .env` before `docker compose config` and rollout.
@@ -9,6 +9,6 @@ services:
# decomposeds3 specific settings
STORAGE_USERS_DECOMPOSEDS3_ENDPOINT: ${DECOMPOSEDS3_ENDPOINT:-http://minio:9000}
STORAGE_USERS_DECOMPOSEDS3_REGION: ${DECOMPOSEDS3_REGION:-default}
STORAGE_USERS_DECOMPOSEDS3_ACCESS_KEY: ${DECOMPOSEDS3_ACCESS_KEY:-qsfera}
STORAGE_USERS_DECOMPOSEDS3_SECRET_KEY: ${DECOMPOSEDS3_SECRET_KEY:-qsfera-secret-key}
STORAGE_USERS_DECOMPOSEDS3_BUCKET: ${DECOMPOSEDS3_BUCKET:-qsfera-bucket}
STORAGE_USERS_DECOMPOSEDS3_ACCESS_KEY: ${DECOMPOSEDS3_ACCESS_KEY:?Set DECOMPOSEDS3_ACCESS_KEY before starting decomposeds3 in production}
STORAGE_USERS_DECOMPOSEDS3_SECRET_KEY: ${DECOMPOSEDS3_SECRET_KEY:?Set DECOMPOSEDS3_SECRET_KEY before starting decomposeds3 in production}
STORAGE_USERS_DECOMPOSEDS3_BUCKET: ${DECOMPOSEDS3_BUCKET:?Set DECOMPOSEDS3_BUCKET before starting decomposeds3 in production}
@@ -0,0 +1,49 @@
## Production-safe qsfera_full environment template.
## Copy to .env and fill every placeholder before running docker compose.
COMPOSE_PATH_SEPARATOR=:
QSFERA=:qsfera.yml
## Public deployment identity.
OC_DOMAIN=<cloud.example.com>
TRAEFIK_ACME_MAIL=<admin@example.com>
## Use the production image and pin a release tag for repeatable rollouts.
OC_DOCKER_IMAGE=qsfera/qsfera
OC_DOCKER_TAG=<release-tag>
## Production must validate TLS certificates and must not create demo users.
INSECURE=false
DEMO_USERS=false
## Required by qsfera.yml.
ADMIN_PASSWORD=<replace-with-strong-password>
## Optional: enable Keycloak-backed production identity.
## Uncomment KEYCLOAK and fill all values before enabling it.
#KEYCLOAK=:keycloak.yml
KEYCLOAK_DOMAIN=<keycloak.example.com>
KEYCLOAK_REALM=qsfera
KEYCLOAK_POSTGRES_PASSWORD=<replace-with-strong-password>
KEYCLOAK_ADMIN_USER=<replace-with-admin-user>
KEYCLOAK_ADMIN_PASSWORD=<replace-with-strong-password>
## Optional: enable S3-backed user storage.
## Uncomment DECOMPOSEDS3 and fill all values before enabling it.
#DECOMPOSEDS3=:decomposeds3.yml
DECOMPOSEDS3_ENDPOINT=<https://s3.example.com>
DECOMPOSEDS3_REGION=<region>
DECOMPOSEDS3_ACCESS_KEY=<replace-with-access-key>
DECOMPOSEDS3_SECRET_KEY=<replace-with-secret-key>
DECOMPOSEDS3_BUCKET=<bucket>
## Optional: local MinIO is intended for test/lab installs, not public production.
#DECOMPOSEDS3_MINIO=:minio.yml
#MINIO_DOMAIN=<minio.example.com>
## Optional: use host paths for backup-friendly config and data storage.
#OC_CONFIG_DIR=/srv/qsfera/config
#OC_DATA_DIR=/srv/qsfera/data
## Keep this last: it assembles the selected compose files.
COMPOSE_FILE=docker-compose.yml${QSFERA:-}${DECOMPOSEDS3:-}${DECOMPOSEDS3_MINIO:-}${KEYCLOAK:-}
@@ -33,7 +33,7 @@ services:
environment:
POSTGRES_DB: keycloak
POSTGRES_USER: keycloak
POSTGRES_PASSWORD: keycloak
POSTGRES_PASSWORD: ${KEYCLOAK_POSTGRES_PASSWORD:?Set KEYCLOAK_POSTGRES_PASSWORD before starting Keycloak in production}
logging:
driver: ${LOG_DRIVER:-local}
restart: always
@@ -53,10 +53,10 @@ services:
KC_DB: postgres
KC_DB_URL: "jdbc:postgresql://postgres:5432/keycloak"
KC_DB_USERNAME: keycloak
KC_DB_PASSWORD: keycloak
KC_DB_PASSWORD: ${KEYCLOAK_POSTGRES_PASSWORD:?Set KEYCLOAK_POSTGRES_PASSWORD before starting Keycloak in production}
KC_FEATURES: impersonation
KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN_USER:-admin}
KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD:-admin}
KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN_USER:?Set KEYCLOAK_ADMIN_USER before starting Keycloak in production}
KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD:?Set KEYCLOAK_ADMIN_PASSWORD before starting Keycloak in production}
labels:
- "traefik.enable=true"
- "traefik.http.routers.keycloak.entrypoints=https"
@@ -10,13 +10,13 @@ services:
command:
[
"-c",
"mkdir -p /data/${DECOMPOSEDS3_BUCKET:-qsfera-bucket} && minio server --console-address ':9001' /data",
"mkdir -p /data/${DECOMPOSEDS3_BUCKET:?Set DECOMPOSEDS3_BUCKET before starting MinIO in production} && minio server --console-address ':9001' /data",
]
volumes:
- minio-data:/data
environment:
MINIO_ROOT_USER: ${DECOMPOSEDS3_ACCESS_KEY:-qsfera}
MINIO_ROOT_PASSWORD: ${DECOMPOSEDS3_SECRET_KEY:-qsfera-secret-key}
MINIO_ROOT_USER: ${DECOMPOSEDS3_ACCESS_KEY:?Set DECOMPOSEDS3_ACCESS_KEY before starting MinIO in production}
MINIO_ROOT_PASSWORD: ${DECOMPOSEDS3_SECRET_KEY:?Set DECOMPOSEDS3_SECRET_KEY before starting MinIO in production}
labels:
- "traefik.enable=true"
- "traefik.http.routers.minio.entrypoints=https"
@@ -33,7 +33,7 @@ services:
# basic auth (not recommended, but needed for eg. WebDav clients that do not support OpenID Connect)
PROXY_ENABLE_BASIC_AUTH: "${PROXY_ENABLE_BASIC_AUTH:-false}"
# admin user password
IDM_ADMIN_PASSWORD: "${ADMIN_PASSWORD:-admin}" # this overrides the admin password from the configuration file
IDM_ADMIN_PASSWORD: "${ADMIN_PASSWORD:?Set ADMIN_PASSWORD before starting qsfera_full in production}" # this overrides the admin password from the configuration file
# demo users
IDM_CREATE_DEMO_USERS: "${DEMO_USERS:-false}"
# idp login form settings
@@ -0,0 +1,155 @@
#!/usr/bin/env sh
set -eu
ENV_FILE="${1:-.env}"
errors=0
warnings=0
if [ ! -f "$ENV_FILE" ]; then
echo "ERROR: env file not found: $ENV_FILE" >&2
exit 1
fi
value() {
key="$1"
awk -v key="$key" '
/^[[:space:]]*#/ || /^[[:space:]]*$/ { next }
{
line = $0
sub(/\r$/, "", line)
split(line, parts, "=")
current = parts[1]
gsub(/^[ \t]+|[ \t]+$/, "", current)
if (current == key) {
sub(/^[^=]*=/, "", line)
gsub(/^"/, "", line)
gsub(/"$/, "", line)
gsub(/^'\''/, "", line)
gsub(/'\''$/, "", line)
print line
exit
}
}
' "$ENV_FILE"
}
is_placeholder() {
case "${1:-}" in
""|"<"*">"|*example.com*|"change-me"|"changeme"|"password"|"secret")
return 0
;;
*)
return 1
;;
esac
}
error() {
errors=$((errors + 1))
echo "ERROR: $1" >&2
}
warn() {
warnings=$((warnings + 1))
echo "WARN: $1" >&2
}
require_value() {
key="$1"
label="${2:-$1}"
current="$(value "$key")"
if is_placeholder "$current"; then
error "$label must be set to a production value."
fi
}
require_secret() {
key="$1"
current="$(value "$key")"
if is_placeholder "$current"; then
error "$key must be set."
return
fi
case "$current" in
admin|demo|keycloak|qsfera|qsfera-secret-key)
error "$key uses a known demo/default value."
return
;;
esac
if [ "${#current}" -lt 16 ]; then
error "$key must be at least 16 characters."
fi
}
enabled() {
current="$(value "$1")"
[ -n "$current" ]
}
require_value OC_DOMAIN "OC_DOMAIN"
require_value TRAEFIK_ACME_MAIL "TRAEFIK_ACME_MAIL"
require_secret ADMIN_PASSWORD
case "$(value INSECURE)" in
true|TRUE|1|yes|YES)
error "INSECURE must be false for production."
;;
esac
case "$(value DEMO_USERS)" in
true|TRUE|1|yes|YES)
error "DEMO_USERS must be false for production."
;;
esac
case "$(value OC_DOCKER_IMAGE)" in
qsfera/qsfera)
;;
"")
warn "OC_DOCKER_IMAGE is empty; compose default must be verified before rollout."
;;
*)
error "OC_DOCKER_IMAGE must be qsfera/qsfera for production."
;;
esac
case "$(value OC_DOCKER_TAG)" in
""|latest|"<"*">")
error "OC_DOCKER_TAG must pin a concrete production release tag."
;;
esac
if ! enabled QSFERA; then
error "QSFERA=:qsfera.yml must be enabled."
fi
if enabled DECOMPOSEDS3; then
require_value DECOMPOSEDS3_ENDPOINT "DECOMPOSEDS3_ENDPOINT"
require_value DECOMPOSEDS3_REGION "DECOMPOSEDS3_REGION"
require_secret DECOMPOSEDS3_ACCESS_KEY
require_secret DECOMPOSEDS3_SECRET_KEY
require_value DECOMPOSEDS3_BUCKET "DECOMPOSEDS3_BUCKET"
fi
if enabled DECOMPOSEDS3_MINIO; then
error "DECOMPOSEDS3_MINIO is for test/lab installs and must not be enabled for public production."
fi
if enabled KEYCLOAK; then
require_value KEYCLOAK_DOMAIN "KEYCLOAK_DOMAIN"
require_value KEYCLOAK_REALM "KEYCLOAK_REALM"
require_secret KEYCLOAK_POSTGRES_PASSWORD
require_value KEYCLOAK_ADMIN_USER "KEYCLOAK_ADMIN_USER"
require_secret KEYCLOAK_ADMIN_PASSWORD
fi
if [ -z "$(value OC_CONFIG_DIR)" ] || [ -z "$(value OC_DATA_DIR)" ]; then
warn "OC_CONFIG_DIR and OC_DATA_DIR are not both set; docker volumes are harder to back up and restore."
fi
if [ "$errors" -gt 0 ]; then
echo "Production env validation failed: $errors error(s), $warnings warning(s)." >&2
exit 1
fi
echo "Production env validation passed: $warnings warning(s)."