diff --git a/backend/Dockerfile b/backend/Dockerfile index 8a2d2021..cda3640d 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -31,4 +31,4 @@ COPY --from=builder /app/prisma ./prisma COPY --from=builder /app/dist ./dist EXPOSE 8080 -CMD ["sh", "-c", "until ./node_modules/.bin/prisma migrate deploy; do echo 'Migration failed, retrying in 5s...'; sleep 5; done && node dist/main"] +CMD ["sh", "-c", "if [ \"${SKIP_MIGRATION:-false}\" != \"true\" ]; then echo 'Running automatic Prisma migration...'; until ./node_modules/.bin/prisma migrate deploy --schema prisma/schema.prisma; do echo 'Migration failed, retrying in 5s...'; sleep 5; done; else echo 'Skipping automatic Prisma migration (SKIP_MIGRATION=true).'; fi && node dist/main"] diff --git a/compose.yml b/compose.yml index 8deb851a..79cd2618 100644 --- a/compose.yml +++ b/compose.yml @@ -23,6 +23,7 @@ services: IMPORTER_SERVICE_URL: "http://importer-api:3001" RECEIPT_TRACE_DECISIONS: "${RECEIPT_TRACE_DECISIONS:-0}" PRISMA_LOG_QUERIES: "${PRISMA_LOG_QUERIES:-0}" + SKIP_MIGRATION: "${SKIP_MIGRATION:-false}" volumes: - recipe_images:/app/recipe-images depends_on: diff --git a/deploy.sh b/deploy.sh index 9d4091e5..7ff5d226 100755 --- a/deploy.sh +++ b/deploy.sh @@ -9,15 +9,17 @@ # ./deploy.sh --flutter – bygg bara flutter web-app # ./deploy.sh --importer – bygg bara importer-microservice # ./deploy.sh --seed – kör full seed på databasen (opt-in) -# ./deploy.sh --migrate – kör Prisma-migrationer (opt-in) +# ./deploy.sh --migrate – kör Prisma-migrationer explicit (opt-in) +# ./deploy.sh --skip-migration – hoppa över automatisk startup-migrering i recipe-api # ./deploy.sh --clean-database – kör underhålls-SQL som rensar data men behåller kategorier -# ./deploy.sh --pull-always – kontrollera uppdateringar för basimages (flutter:3.41.9, node:24.15.0 etc) +# ./deploy.sh --pull-always – kontrollera uppdateringar för basimages # ./deploy.sh --backend --seed – kombinera flaggor fritt (git pull körs alltid) -set -e +set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR" +START_TS="$(date +%s)" # ── Flaggor ────────────────────────────────────────────────────────────────── BUILD_BACKEND=false @@ -26,23 +28,94 @@ BUILD_IMPORTER=false RUN_SEED=false RUN_MIGRATE=false RUN_CLEAN_DATABASE=false +SKIP_MIGRATION=false PULL_IMAGES=false # --pull=false är standard (snabbt) BUILD_ALL=true # om inga specifika tjänster anges, bygg allt +# ── Hjälpfunktioner ─────────────────────────────────────────────────────────── +info() { echo "[INFO] $*"; } +warn() { echo "[WARN] $*"; } +fatal() { echo "[ERROR] $*"; exit 1; } + +require_cmd() { + command -v "$1" >/dev/null 2>&1 || fatal "Kommando saknas: $1" +} + +read_env_value() { + local key="$1" + local line + line=$(grep -E "^${key}=" .env | tail -n 1 || true) + line="${line#*=}" + line="${line%\"}" + line="${line#\"}" + line="${line%\'}" + line="${line#\'}" + printf '%s' "$line" +} + +wait_for_backend_prisma() { + info "Väntar på att backend är redo för Prisma-kommandon..." + for i in $(seq 1 30); do + if docker exec recipe-api sh -lc "test -f /app/prisma/schema.prisma" >/dev/null 2>&1; then + return 0 + fi + info " ...försök $i/30" + sleep 2 + done + return 1 +} + +wait_for_db() { + local root_password="$1" + info "Väntar på att databasen är redo..." + for i in $(seq 1 30); do + if docker exec recipe-db mariadb-admin ping -h 127.0.0.1 -uroot -p"$root_password" --silent 2>/dev/null; then + return 0 + fi + info " ...försök $i/30" + sleep 2 + done + return 1 +} + +run_prisma_migrate_deploy() { + local output + info "Kör Prisma-migrationer (deploy)..." + info " ▶ Kör: npx prisma migrate deploy" + + if ! output=$(docker exec recipe-api sh -lc "cd /app && npx prisma migrate deploy --schema prisma/schema.prisma" 2>&1); then + echo "$output" + fatal "Prisma migration misslyckades." + fi + + echo "$output" + + if echo "$output" | grep -qi "No pending migrations"; then + info "Migration-status: inga väntande migrationer." + elif echo "$output" | grep -qi "Applying migration"; then + info "Migration-status: minst en migration applicerades." + else + warn "Migration-status: kunde inte avgöra om nya migrationer applicerades." + fi + + info "Migrationer slutförda utan fel." +} + for arg in "$@"; do case "$arg" in - --backend) BUILD_BACKEND=true; BUILD_ALL=false ;; - --flutter) BUILD_FLUTTER=true; BUILD_ALL=false ;; - --importer) BUILD_IMPORTER=true; BUILD_ALL=false ;; - --seed) RUN_SEED=true ;; - --migrate) RUN_MIGRATE=true; BUILD_BACKEND=true; BUILD_ALL=false ;; - --clean-database) RUN_CLEAN_DATABASE=true; BUILD_BACKEND=true; BUILD_ALL=false ;; - --pull-always) PULL_IMAGES=true ;; + --backend) BUILD_BACKEND=true; BUILD_ALL=false ;; + --flutter) BUILD_FLUTTER=true; BUILD_ALL=false ;; + --importer) BUILD_IMPORTER=true; BUILD_ALL=false ;; + --seed) RUN_SEED=true ;; + --migrate) RUN_MIGRATE=true; BUILD_BACKEND=true; BUILD_ALL=false ;; + --skip-migration) SKIP_MIGRATION=true ;; + --clean-database) RUN_CLEAN_DATABASE=true; BUILD_BACKEND=true; BUILD_ALL=false ;; + --pull-always) PULL_IMAGES=true ;; --help|-h) sed -n '/^# Användning:/,/^[^#]/p' "$0" | grep '^#' | sed 's/^# \?//' exit 0 ;; - *) echo "Okänd flagga: $arg (--help för hjälp)"; exit 1 ;; + *) fatal "Okänd flagga: $arg (--help för hjälp)" ;; esac done @@ -52,99 +125,106 @@ if [ "$BUILD_ALL" = true ]; then BUILD_IMPORTER=true fi -# ── Validering ──────────────────────────────────────────────────────────────── -if [ ! -f ".env" ]; then - echo "Fel: .env saknas. Kopiera .env.example och fyll i värdena:" - echo " cp .env.example .env && nano .env" - exit 1 +# Om explicit migration begärs, stäng av automigrering i containern för att undvika dubbelkörning. +if [ "$RUN_MIGRATE" = true ]; then + SKIP_MIGRATION=true fi +# ── Validering ──────────────────────────────────────────────────────────────── +[ -f ".env" ] || fatal ".env saknas. Kör: cp .env.example .env && nano .env" + +require_cmd git +require_cmd docker + +if [ "$BUILD_BACKEND" = true ] || [ "$RUN_SEED" = true ] || [ "$RUN_CLEAN_DATABASE" = true ] || [ "$RUN_MIGRATE" = true ]; then + require_cmd grep +fi + +export SKIP_MIGRATION +COMPOSE_CMD=(docker compose -f compose.yml -f compose.flutter.yml) + # ── Git pull ────────────────────────────────────────────────────────────────── -echo "Hämtar senaste kod (recipe-app)..." +info "Hämtar senaste kod (recipe-app)..." git pull origin main -echo "Hämtar senaste kod (microservice-importer)..." -(cd "$SCRIPT_DIR/../microservice-importer" && git pull origin main) - -# ── Bygger valda tjänster ───────────────────────────────────────────────────── -COMPOSE="docker compose -f compose.yml -f compose.flutter.yml" -SERVICES="" - -[ "$BUILD_BACKEND" = true ] && SERVICES="$SERVICES recipe-api" -[ "$BUILD_FLUTTER" = true ] && SERVICES="$SERVICES recipe-flutter" -[ "$BUILD_IMPORTER" = true ] && SERVICES="$SERVICES importer-api" - -echo "Bygger: ${SERVICES:-alla tjänster}..." -if [ "$PULL_IMAGES" = true ]; then - # Kontrollera om nya versioner av basimages finns på Docker Hub / ghcr.io - echo " (kontrollerar uppdateringar för basimages...)" - $COMPOSE build $SERVICES +if [ -d "$SCRIPT_DIR/../microservice-importer/.git" ]; then + info "Hämtar senaste kod (microservice-importer)..." + (cd "$SCRIPT_DIR/../microservice-importer" && git pull origin main) else - # Standard: använd lokala cachade images, snabbare - $COMPOSE build --pull=false $SERVICES + warn "microservice-importer repo hittades inte på förväntad path, hoppar över git pull där." fi -echo "Startar tjänster..." -$COMPOSE up -d +# ── Bygger valda tjänster ───────────────────────────────────────────────────── +SERVICES=() +[ "$BUILD_BACKEND" = true ] && SERVICES+=(recipe-api) +[ "$BUILD_FLUTTER" = true ] && SERVICES+=(recipe-flutter) +[ "$BUILD_IMPORTER" = true ] && SERVICES+=(importer-api) -# ── Prisma migreringar och databasrensning (opt-in) ─────────────────────────── +if [ "${#SERVICES[@]}" -eq 0 ]; then + info "Bygger: alla tjänster..." +else + info "Bygger: ${SERVICES[*]}" +fi + +if [ "$PULL_IMAGES" = true ]; then + info "(kontrollerar uppdateringar för basimages...)" + "${COMPOSE_CMD[@]}" build "${SERVICES[@]}" +else + "${COMPOSE_CMD[@]}" build --pull=false "${SERVICES[@]}" +fi + +info "Startar tjänster..." +"${COMPOSE_CMD[@]}" up -d + +# ── Prisma migreringar och databasrensning (opt-in) ───────────────────────── if [ "$RUN_MIGRATE" = true ] || [ "$RUN_CLEAN_DATABASE" = true ]; then CLEAN_SQL_FILE="backend/prisma/maintenance/clean-database.sql" - echo "Väntar på att backend är redo för Prisma-kommandon..." - for i in $(seq 1 30); do - if docker exec recipe-api sh -lc "test -f /app/prisma/schema.prisma" >/dev/null 2>&1; then - break - fi - echo " ...försök $i/30" - sleep 2 - done + wait_for_backend_prisma || fatal "Backend blev inte redo för Prisma-kommandon i tid." if [ "$RUN_MIGRATE" = true ]; then - echo "Kör Prisma-migrationer (deploy)..." - docker exec recipe-api sh -lc "cd /app && npx prisma migrate deploy --schema prisma/schema.prisma" + run_prisma_migrate_deploy fi if [ "$RUN_CLEAN_DATABASE" = true ]; then - if [ ! -f "$CLEAN_SQL_FILE" ]; then - echo "Fel: saknar $CLEAN_SQL_FILE" - exit 1 - fi + [ -f "$CLEAN_SQL_FILE" ] || fatal "Saknar $CLEAN_SQL_FILE" - MARIADB_ROOT_PASSWORD=$(grep MARIADB_ROOT_PASSWORD .env | cut -d '=' -f2 | tr -d '"' | tr -d "'") - MARIADB_DATABASE=$(grep MARIADB_DATABASE .env | cut -d '=' -f2 | tr -d '"' | tr -d "'") + MARIADB_ROOT_PASSWORD="$(read_env_value MARIADB_ROOT_PASSWORD)" + MARIADB_DATABASE="$(read_env_value MARIADB_DATABASE)" - echo "Kör databasrensning från $CLEAN_SQL_FILE ..." + [ -n "$MARIADB_ROOT_PASSWORD" ] || fatal "MARIADB_ROOT_PASSWORD saknas i .env" + [ -n "$MARIADB_DATABASE" ] || fatal "MARIADB_DATABASE saknas i .env" + + info "Kör databasrensning från $CLEAN_SQL_FILE ..." docker exec -i recipe-db mariadb -uroot -p"$MARIADB_ROOT_PASSWORD" "$MARIADB_DATABASE" < "$CLEAN_SQL_FILE" - echo "Databasrensning klar (kategorier bevarade enligt SQL-filen)." + info "Databasrensning klar (kategorier bevarade enligt SQL-filen)." fi - echo "Uppdaterar Prisma Client..." + info "Uppdaterar Prisma Client..." docker exec recipe-api sh -lc "cd /app && npx prisma generate --schema prisma/schema.prisma" fi # ── Seed (opt-in) ───────────────────────────────────────────────────────────── if [ "$RUN_SEED" = true ]; then - MARIADB_ROOT_PASSWORD=$(grep MARIADB_ROOT_PASSWORD .env | cut -d '=' -f2 | tr -d '"' | tr -d "'") - MARIADB_DATABASE=$(grep MARIADB_DATABASE .env | cut -d '=' -f2 | tr -d '"' | tr -d "'") + MARIADB_ROOT_PASSWORD="$(read_env_value MARIADB_ROOT_PASSWORD)" + MARIADB_DATABASE="$(read_env_value MARIADB_DATABASE)" - echo "Väntar på att databasen är redo..." - for i in $(seq 1 30); do - if docker exec recipe-db mariadb-admin ping -h 127.0.0.1 -uroot -p"$MARIADB_ROOT_PASSWORD" --silent 2>/dev/null; then - break - fi - echo " ...försök $i/30" - sleep 2 - done + [ -n "$MARIADB_ROOT_PASSWORD" ] || fatal "MARIADB_ROOT_PASSWORD saknas i .env" + [ -n "$MARIADB_DATABASE" ] || fatal "MARIADB_DATABASE saknas i .env" + + wait_for_db "$MARIADB_ROOT_PASSWORD" || fatal "Databasen blev inte redo i tid." if [ -f "db/seeds/seed_all.sql" ]; then - docker exec -i recipe-db mariadb -uroot -p"$MARIADB_ROOT_PASSWORD" "$MARIADB_DATABASE" \ - < db/seeds/seed_all.sql - echo "Full seed klar." + docker exec -i recipe-db mariadb -uroot -p"$MARIADB_ROOT_PASSWORD" "$MARIADB_DATABASE" < db/seeds/seed_all.sql + info "Full seed klar." else - echo "Ingen db/seeds/seed_all.sql hittades — hoppar över seed." + warn "Ingen db/seeds/seed_all.sql hittades — hoppar över seed." fi fi -echo "Status:" -$COMPOSE ps +info "Status:" +"${COMPOSE_CMD[@]}" ps + +END_TS="$(date +%s)" +DURATION="$((END_TS - START_TS))" +info "Deploy klart på ${DURATION}s."