diff --git a/.gitignore b/.gitignore index 668175a..5f56cc3 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,8 @@ infra/recordings/ infra/.env infra/.env.production.generated infra/garage/dev-credentials.env +infra/garage/prod-credentials.env +infra/garage/garage.prod.toml # OS .DS_Store diff --git a/docs/REPLAY_MODULE.md b/docs/REPLAY_MODULE.md index 3814ab7..abbfa22 100644 --- a/docs/REPLAY_MODULE.md +++ b/docs/REPLAY_MODULE.md @@ -74,6 +74,14 @@ docker compose up -d rails sidekiq docker compose exec -T rails bundle exec rails replay:e2e_garage ``` +### Produzione (`/opt/matchlivetv`) + +```bash +cd infra +bash scripts/setup_garage_production.sh # garage.prod.toml, bucket, chiavi → .env +# Rails/Sidekiq usano REPLAY_STORAGE_ENDPOINT=http://garage:3900 (rete Docker) +``` + **Credenziali in `.env`:** non compilarle a mano in `.env.example`. Lo script scrive in `infra/.env`: - `REPLAY_STORAGE_ACCESS_KEY_ID` = Key ID Garage (es. `GK...`) - `REPLAY_STORAGE_SECRET_ACCESS_KEY` = Secret mostrato una sola volta alla creazione chiave diff --git a/infra/.env.production.example b/infra/.env.production.example index 63cbbc2..23129ea 100644 --- a/infra/.env.production.example +++ b/infra/.env.production.example @@ -50,3 +50,12 @@ SMTP_PASSWORD= SMTP_AUTH=plain SMTP_STARTTLS=true PASSWORD_RESET_EXPIRY_HOURS=2 + +# Replay storage — Garage (obbligatorio in produzione per Premium replay) +# Dopo il primo deploy: bash scripts/setup_garage_production.sh (crea garage.prod.toml + chiavi) +REPLAY_STORAGE_ENDPOINT=http://garage:3900 +REPLAY_STORAGE_BUCKET=matchlivetv-replays +REPLAY_STORAGE_REGION=garage +REPLAY_STORAGE_ACCESS_KEY_ID= +REPLAY_STORAGE_SECRET_ACCESS_KEY= +REPLAY_STORAGE_FORCE_PATH_STYLE=true diff --git a/infra/docker-compose.prod.yml b/infra/docker-compose.prod.yml index aaf792f..af57683 100644 --- a/infra/docker-compose.prod.yml +++ b/infra/docker-compose.prod.yml @@ -111,6 +111,8 @@ services: condition: service_healthy mediamtx: condition: service_started + garage: + condition: service_started volumes: - recordings:/recordings healthcheck: @@ -151,10 +153,24 @@ services: depends_on: rails: condition: service_healthy + garage: + condition: service_started volumes: - recordings:/recordings + garage: + image: dxflrs/garage:v1.0.1 + restart: unless-stopped + # API S3 solo rete Docker (rails/sidekiq → http://garage:3900). Non pubblicare 3900 su WAN. + volumes: + - garage_meta:/var/lib/garage/meta + - garage_data:/var/lib/garage/data + - ./garage/garage.prod.toml:/etc/garage.toml:ro + command: ["/garage", "-c", "/etc/garage.toml", "server"] + volumes: postgres_data: redis_data: recordings: + garage_meta: + garage_data: diff --git a/infra/garage/garage.prod.toml.example b/infra/garage/garage.prod.toml.example new file mode 100644 index 0000000..16bfdaa --- /dev/null +++ b/infra/garage/garage.prod.toml.example @@ -0,0 +1,22 @@ +# Copia in garage.prod.toml sul server e sostituisci i placeholder (o usa scripts/ensure_garage_prod_config.sh) +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 = "garage:3901" +rpc_secret = "CHANGE_ME_RPC_SECRET_64_HEX" + +[s3_api] +api_bind_addr = "[::]:3900" +s3_region = "garage" +root_domain = ".s3.matchlivetv.internal" + +[s3_web] +bind_addr = "[::]:3902" +root_domain = ".web.matchlivetv.internal" + +[admin] +api_bind_addr = "[::]:3903" +admin_token = "CHANGE_ME_ADMIN_TOKEN_64_HEX" diff --git a/infra/scripts/ensure_garage_prod_config.sh b/infra/scripts/ensure_garage_prod_config.sh new file mode 100755 index 0000000..8e0564e --- /dev/null +++ b/infra/scripts/ensure_garage_prod_config.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# Crea garage/garage.prod.toml con secret casuali se mancante (solo produzione). +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +DEST="${ROOT}/garage/garage.prod.toml" +TEMPLATE="${ROOT}/garage/garage.prod.toml.example" + +if [ -f "$DEST" ]; then + echo "Config Garage produzione già presente: $DEST" + exit 0 +fi + +if [ ! -f "$TEMPLATE" ]; then + echo "Manca $TEMPLATE" + exit 1 +fi + +RPC="$(openssl rand -hex 32)" +ADMIN="$(openssl rand -hex 32)" +sed -e "s/CHANGE_ME_RPC_SECRET_64_HEX/${RPC}/" \ + -e "s/CHANGE_ME_ADMIN_TOKEN_64_HEX/${ADMIN}/" \ + "$TEMPLATE" > "$DEST" +chmod 600 "$DEST" +echo "Creato $DEST" diff --git a/infra/scripts/setup_garage_production.sh b/infra/scripts/setup_garage_production.sh new file mode 100755 index 0000000..3f8ffae --- /dev/null +++ b/infra/scripts/setup_garage_production.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# Garage replay storage su server produzione (/opt/matchlivetv/infra) +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$ROOT" + +export COMPOSE_FILE="docker-compose.prod.yml" +export ENV_FILE="${ROOT}/.env" +export CREDS_FILE="${ROOT}/garage/prod-credentials.env" + +bash "${ROOT}/scripts/ensure_garage_prod_config.sh" + +echo "Avvio Garage..." +docker compose -f docker-compose.prod.yml --env-file .env up -d garage + +bash "${ROOT}/scripts/setup_garage_replays.sh" + +echo "Riavvio Rails e Sidekiq..." +docker compose -f docker-compose.prod.yml --env-file .env up -d rails sidekiq + +echo "Verifica storage:" +docker compose -f docker-compose.prod.yml --env-file .env exec -T rails bundle exec rails runner \ + 'puts({local: MatchLiveTv.replay_storage_local?, backend: Recordings::Storage.new.backend_name}.inspect)' diff --git a/infra/scripts/setup_garage_replays.sh b/infra/scripts/setup_garage_replays.sh index b4f09c5..58aab1f 100755 --- a/infra/scripts/setup_garage_replays.sh +++ b/infra/scripts/setup_garage_replays.sh @@ -3,11 +3,23 @@ set -euo pipefail ROOT="$(cd "$(dirname "$0")/.." && pwd)" -GARAGE_CONTAINER="${GARAGE_CONTAINER:-infra-garage-1}" +COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}" +GARAGE_CONTAINER="${GARAGE_CONTAINER:-}" BUCKET="${REPLAY_STORAGE_BUCKET:-matchlivetv-replays}" KEY_NAME="${REPLAY_STORAGE_KEY_NAME:-replay-uploader}" -CREDS_FILE="${ROOT}/garage/dev-credentials.env" -ENV_FILE="${ROOT}/.env" +CREDS_FILE="${CREDS_FILE:-${ROOT}/garage/dev-credentials.env}" +ENV_FILE="${ENV_FILE:-${ROOT}/.env}" + +compose() { + docker compose -f "${ROOT}/${COMPOSE_FILE}" --env-file "${ENV_FILE}" "$@" +} + +resolve_garage_container() { + if [ -n "$GARAGE_CONTAINER" ]; then + return 0 + fi + GARAGE_CONTAINER="$(docker ps --format '{{.Names}}' | grep -E 'garage-1$' | head -1 || true)" +} gexec() { docker exec "$GARAGE_CONTAINER" /garage "$@" @@ -21,7 +33,7 @@ wait_garage() { fi sleep 1 done - echo "Garage non risponde. Avvia: cd $ROOT && docker compose up -d garage" + echo "Garage non risponde. Avvia: cd $ROOT && docker compose -f $COMPOSE_FILE up -d garage" exit 1 } @@ -126,9 +138,11 @@ EOF main() { cd "$ROOT" - if ! docker ps --format '{{.Names}}' | grep -q "^${GARAGE_CONTAINER}$"; then - echo "Avvio container garage..." - docker compose up -d garage + resolve_garage_container + if [ -z "$GARAGE_CONTAINER" ] || ! docker ps --format '{{.Names}}' | grep -q "^${GARAGE_CONTAINER}$"; then + echo "Avvio container garage ($COMPOSE_FILE)..." + compose up -d garage + resolve_garage_container fi wait_garage ensure_layout @@ -137,7 +151,7 @@ main() { write_credentials merge_into_env echo "" - echo "Riavvia app: docker compose up -d rails sidekiq" + echo "Riavvia app: docker compose -f $COMPOSE_FILE up -d rails sidekiq" } main "$@"