#!/usr/bin/env bash # # Sprawdzenie konfiguracji przed uruchomieniem instancji u klienta. # # ./scripts/check-config.sh # sprawdza .env.production # ENV_FILE=.env ./scripts/check-config.sh # inny plik # ./scripts/check-config.sh --skip-db # bez pytania bazy (przed pierwszym startem) # # ODMAWIA STARTU, ZAMIAST OSTRZEC I PRZEPUSCIC. # --------------------------------------------------------------------------- # Kazdy sprawdzany warunek to blad, ktory na produkcji kosztuje wiecej niz # nieudany start: sekret z przykladu oznacza, ze kazdy moze podrobic token, # adres z "localhost" oznacza martwe linki w kazdym wyslanym mailu, a haslo # przykladowe oznacza konto administratora otwarte dla kogokolwiek, kto # widzial repozytorium. # # Kod wyjscia: 0 gdy wszystko w porzadku, 1 gdy cokolwiek blokuje start. set -uo pipefail ENV_FILE="${ENV_FILE:-.env.production}" COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.prod.yml}" EXAMPLE_FILE="${EXAMPLE_FILE:-.env.production.example}" SKIP_DB=0 for arg in "$@"; do case "$arg" in --skip-db) SKIP_DB=1 ;; -h|--help) sed -n '2,20p' "$0"; exit 0 ;; esac done ERRORS=0 WARNINGS=0 red() { printf '\033[31m%s\033[0m\n' "$*"; } yellow() { printf '\033[33m%s\033[0m\n' "$*"; } green() { printf '\033[32m%s\033[0m\n' "$*"; } fail() { red " BLAD: $1" [ $# -gt 1 ] && echo " $2" ERRORS=$((ERRORS + 1)) } warn() { yellow " UWAGA: $1" [ $# -gt 1 ] && echo " $2" WARNINGS=$((WARNINGS + 1)) } ok() { echo " ok: $1"; } # Wartosc zmiennej z pliku .env. Obsluguje wartosci w cudzyslowach. value_of() { local key="$1" file="$2" grep -E "^${key}=" "$file" 2>/dev/null \ | tail -n1 \ | cut -d= -f2- \ | sed -e 's/^"//' -e 's/"$//' -e "s/^'//" -e "s/'$//" } echo "==========================================================" echo " Sprawdzenie konfiguracji: ${ENV_FILE}" echo "==========================================================" if [ ! -f "${ENV_FILE}" ]; then red "BLAD: brak pliku ${ENV_FILE}" echo echo "Skopiuj wzor i uzupelnij:" echo " cp ${EXAMPLE_FILE} ${ENV_FILE}" echo " chmod 600 ${ENV_FILE}" exit 1 fi # --- uprawnienia pliku z sekretami --- echo echo "[1/5] Plik konfiguracji" if [ "$(uname -s)" != "MINGW64_NT"* ] && command -v stat >/dev/null 2>&1; then PERMS="$(stat -c '%a' "${ENV_FILE}" 2>/dev/null || echo '')" case "${PERMS}" in 600|400) ok "uprawnienia ${PERMS}" ;; "") ;; # systemy bez stat -c, pomijamy *) warn "plik ma uprawnienia ${PERMS}, zalecane 600" \ "chmod 600 ${ENV_FILE}" ;; esac fi # --- JWT_SECRET --- echo echo "[2/5] Sekret podpisywania tokenow" JWT_SECRET="$(value_of JWT_SECRET "${ENV_FILE}")" JWT_EXAMPLE="$(value_of JWT_SECRET "${EXAMPLE_FILE}" 2>/dev/null || echo '')" if [ -z "${JWT_SECRET}" ]; then fail "JWT_SECRET jest pusty" \ "Wygeneruj: openssl rand -base64 48" elif [ "${#JWT_SECRET}" -lt 32 ]; then fail "JWT_SECRET ma ${#JWT_SECRET} znakow, wymagane minimum 32" \ "Krotszy klucz da sie zlamac. Wygeneruj: openssl rand -base64 48" elif [ -n "${JWT_EXAMPLE}" ] && [ "${JWT_SECRET}" = "${JWT_EXAMPLE}" ]; then fail "JWT_SECRET jest rowny wartosci z ${EXAMPLE_FILE}" \ "Kazdy, kto widzial repozytorium, moze podrobic token administratora." else ok "JWT_SECRET ustawiony (${#JWT_SECRET} znakow)" fi # --- ADMIN_PASSWORD --- echo echo "[3/5] Konto administratora" ADMIN_PASSWORD="$(value_of ADMIN_PASSWORD "${ENV_FILE}")" ADMIN_EXAMPLE="$(value_of ADMIN_PASSWORD "${EXAMPLE_FILE}" 2>/dev/null || echo '')" ADMIN_EMAIL="$(value_of ADMIN_EMAIL "${ENV_FILE}")" if [ -z "${ADMIN_EMAIL}" ]; then fail "ADMIN_EMAIL jest pusty" "Bez adresu nie powstanie pierwsze konto." else ok "ADMIN_EMAIL = ${ADMIN_EMAIL}" fi if [ -z "${ADMIN_PASSWORD}" ]; then # Puste haslo jest tu POPRAWNE i zalecane: aplikacja losuje je przy # pierwszym starcie i wypisuje raz w logu. To bezpieczniejsze niz haslo # przechowywane w pliku na serwerze. ok "ADMIN_PASSWORD pusty: haslo zostanie wylosowane i wypisane w logu" echo " Po starcie: docker compose logs backend | grep -i haslo" elif [ -n "${ADMIN_EXAMPLE}" ] && [ "${ADMIN_PASSWORD}" = "${ADMIN_EXAMPLE}" ]; then fail "ADMIN_PASSWORD jest rowny wartosci z ${EXAMPLE_FILE}" \ "Konto administratora byloby otwarte dla kazdego, kto widzial repozytorium." elif [ "${#ADMIN_PASSWORD}" -lt 12 ]; then warn "ADMIN_PASSWORD ma ${#ADMIN_PASSWORD} znakow, zalecane minimum 12" \ "Albo zostaw puste, a aplikacja wylosuje haslo." else ok "ADMIN_PASSWORD ustawiony" fi # --- APP_PUBLIC_URL --- echo echo "[4/5] Adres publiczny instancji" APP_PUBLIC_URL="$(value_of APP_PUBLIC_URL "${ENV_FILE}")" if [ -z "${APP_PUBLIC_URL}" ]; then fail "APP_PUBLIC_URL jest pusty" \ "Z tego adresu powstaja WSZYSTKIE linki w mailach: potwierdzenia, przypomnienia i wypisanie." elif echo "${APP_PUBLIC_URL}" | grep -qiE 'localhost|127\.0\.0\.1|0\.0\.0\.0'; then fail "APP_PUBLIC_URL zawiera adres lokalny: ${APP_PUBLIC_URL}" \ "Uczestnik dostanie link prowadzacy do jego wlasnego komputera." elif ! echo "${APP_PUBLIC_URL}" | grep -qE '^https?://'; then fail "APP_PUBLIC_URL nie zaczyna sie od http:// ani https://: ${APP_PUBLIC_URL}" else case "${APP_PUBLIC_URL}" in */) warn "APP_PUBLIC_URL konczy sie ukosnikiem" "Usun go: ${APP_PUBLIC_URL%/}" ;; esac echo "${APP_PUBLIC_URL}" | grep -qE '^http://' \ && warn "APP_PUBLIC_URL uzywa http, nie https" \ "Tokeny zapisu i sesji poszlyby przez siec otwartym tekstem." ok "APP_PUBLIC_URL = ${APP_PUBLIC_URL}" fi # pozostale sekrety infrastruktury for key in DB_PASSWORD MINIO_SECRET_KEY MINIO_ACCESS_KEY DB_NAME DB_USER MINIO_BUCKET; do v="$(value_of "${key}" "${ENV_FILE}")" ex="$(value_of "${key}" "${EXAMPLE_FILE}" 2>/dev/null || echo '')" if [ -z "${v}" ]; then fail "${key} jest pusty" elif [ -n "${ex}" ] && [ "${v}" = "${ex}" ] && echo "${key}" | grep -q "PASSWORD\|SECRET"; then fail "${key} jest rowny wartosci z ${EXAMPLE_FILE}" fi done # TURN_PASSWORD wymagany tylko przy wlaczonym profilu wideo VIDEO_PROVIDER="$(value_of VIDEO_PROVIDER "${ENV_FILE}")" if [ "${VIDEO_PROVIDER^^}" = "LIVEKIT" ]; then [ -z "$(value_of TURN_PASSWORD "${ENV_FILE}")" ] \ && fail "TURN_PASSWORD jest pusty przy VIDEO_PROVIDER=LIVEKIT" \ "Bez TURN czesc uczestnikow za firewallem firmowym nikogo nie uslyszy." [ -z "$(value_of LIVEKIT_NODE_IP "${ENV_FILE}")" ] \ && warn "LIVEKIT_NODE_IP jest pusty" \ "Na serwerze publicznym ustaw tu jego adres IP, inaczej media moga sie nie zestawic." fi # --- konfiguracja wymagana, prosto z katalogu ustawien --- echo echo "[5/5] Konfiguracja wymagana (katalog SettingKey)" if [ "${SKIP_DB}" -eq 1 ]; then echo " pominieto (--skip-db)" elif ! command -v docker >/dev/null 2>&1; then warn "brak polecenia docker, pomijam sprawdzenie w bazie" else PORT="$(value_of FRONTEND_PORT_EXPOSED "${ENV_FILE}")" PORT="${PORT:-8081}" # Zrodlem prawdy o tym, ktore klucze sa wymagane, jest katalog SettingKey # w backendzie, a nie lista przepisana do tego skryptu: przepisana # rozjechalaby sie z katalogiem przy pierwszym nowym kluczu. RESPONSE="$(curl -fsS --max-time 5 "http://127.0.0.1:${PORT}/api/health/config" 2>/dev/null || echo '')" if [ -z "${RESPONSE}" ]; then echo " instancja nie odpowiada, pomijam" echo " To normalne PRZED pierwszym uruchomieniem." echo " Po starcie uruchom skrypt ponownie, zeby sprawdzic klucze" echo " ustawiane w panelu (SMTP, bramka SMS, dostawca wideo)." elif echo "${RESPONSE}" | grep -q '"ready":true'; then ok "wszystkie wymagane klucze maja wartosc" else echo "${RESPONSE}" \ | tr '{' '\n' \ | grep -o '"key":"[A-Z_]*"' \ | cut -d'"' -f4 \ | while read -r key; do fail "brak wymaganej wartosci: ${key}" \ "Ustaw w panelu albo w ${ENV_FILE}" done # petla `while` biegnie w podpowloce, wiec licznik trzeba podniesc tutaj MISSING_COUNT="$(echo "${RESPONSE}" | grep -o '"key":"[A-Z_]*"' | wc -l)" ERRORS=$((ERRORS + MISSING_COUNT)) fi fi # --- podsumowanie --- echo echo "==========================================================" if [ "${ERRORS}" -gt 0 ]; then red " NIE URUCHAMIAJ: ${ERRORS} bledow, ${WARNINGS} ostrzezen" echo "==========================================================" echo echo "Popraw ${ENV_FILE} i uruchom skrypt ponownie." exit 1 fi if [ "${WARNINGS}" -gt 0 ]; then yellow " Mozna uruchomic: 0 bledow, ${WARNINGS} ostrzezen do przejrzenia" else green " Konfiguracja w porzadku" fi echo "==========================================================" exit 0