Files
akademiawodociagowca/scripts/check-config.sh
T
2026-09-02 08:21:27 +02:00

239 lines
8.8 KiB
Bash

#!/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