Initial commit: monorepo Match Live TV.
Rails API, app Flutter, infrastruttura Docker/MediaMTX, sito marketing e documentazione di deploy. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
24
scripts/deploy/post_bootstrap_deploy.sh
Normal file
24
scripts/deploy/post_bootstrap_deploy.sh
Normal file
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
# Dopo server_bootstrap.sh: avvia lo stack produzione
|
||||
set -euo pipefail
|
||||
|
||||
DEPLOY_ROOT="${DEPLOY_ROOT:-$HOME/matchlivetv}"
|
||||
if [[ -d /opt/matchlivetv ]]; then
|
||||
DEPLOY_ROOT="/opt/matchlivetv"
|
||||
fi
|
||||
|
||||
cd "$DEPLOY_ROOT/infra"
|
||||
|
||||
if [[ ! -f .env ]]; then
|
||||
echo "Manca infra/.env — copia da .env.production.example e compila i secret"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
docker compose -f docker-compose.prod.yml --env-file .env up -d --build
|
||||
|
||||
echo "Attendo healthcheck Rails..."
|
||||
sleep 30
|
||||
curl -sf "http://127.0.0.1:3000/up" && echo " OK" || echo " FAIL — vedi: docker compose -f docker-compose.prod.yml logs rails"
|
||||
|
||||
echo "Seed demo (opzionale, una tantum):"
|
||||
echo " docker compose -f docker-compose.prod.yml exec rails bundle exec rails db:seed"
|
||||
59
scripts/deploy/server_bootstrap.sh
Executable file
59
scripts/deploy/server_bootstrap.sh
Executable file
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env bash
|
||||
# Eseguire UNA VOLTA sul server con sudo (da console SSH o: sudo bash server_bootstrap.sh)
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "${EUID}" -ne 0 ]]; then
|
||||
if sudo -n true 2>/dev/null; then
|
||||
exec sudo -n bash "$0" "$@"
|
||||
else
|
||||
echo "ERRORE: eseguire come root o con sudo senza password:"
|
||||
echo " sudo bash $0"
|
||||
echo ""
|
||||
echo "Per abilitare NOPASSWD per eminux (opzionale):"
|
||||
echo " echo 'eminux ALL=(ALL) NOPASSWD: ALL' | sudo tee /etc/sudoers.d/eminux-nopasswd"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq ca-certificates curl git ufw rsync
|
||||
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
install -m 0755 -d /etc/apt/keyrings
|
||||
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
|
||||
chmod a+r /etc/apt/keyrings/docker.asc
|
||||
|
||||
. /etc/os-release
|
||||
CODENAME="${VERSION_CODENAME:-bookworm}"
|
||||
if [[ "$CODENAME" == "trixie" ]]; then
|
||||
CODENAME="bookworm"
|
||||
fi
|
||||
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian ${CODENAME} stable" \
|
||||
> /etc/apt/sources.list.d/docker.list
|
||||
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||
fi
|
||||
|
||||
usermod -aG docker eminux 2>/dev/null || true
|
||||
systemctl enable --now docker
|
||||
|
||||
mkdir -p /opt/matchlivetv
|
||||
chown -R eminux:eminux /opt/matchlivetv
|
||||
|
||||
# UFW base
|
||||
ufw --force reset || true
|
||||
ufw default deny incoming
|
||||
ufw default allow outgoing
|
||||
ufw allow 22/tcp comment 'SSH'
|
||||
ufw allow 1935/tcp comment 'RTMP MediaMTX'
|
||||
ufw allow 8888/tcp comment 'HLS MediaMTX optional'
|
||||
ufw allow from 192.168.0.0/16 to any port 3000 proto tcp comment 'Rails LAN NPM'
|
||||
ufw --force enable
|
||||
|
||||
docker --version
|
||||
docker compose version
|
||||
echo "Bootstrap completato. Riavvia sessione SSH di eminux per gruppo docker."
|
||||
33
scripts/deploy/sync_to_server.sh
Executable file
33
scripts/deploy/sync_to_server.sh
Executable file
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env bash
|
||||
# Sincronizza il monorepo sul server di produzione (pre-Gitea)
|
||||
set -euo pipefail
|
||||
|
||||
SERVER="${DEPLOY_SERVER:-eminux@192.168.1.146}"
|
||||
DEST="${DEPLOY_PATH:-/opt/matchlivetv}"
|
||||
SRC="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
|
||||
echo "Sync $SRC -> $SERVER:$DEST"
|
||||
|
||||
if ssh "$SERVER" 'command -v rsync >/dev/null 2>&1'; then
|
||||
rsync -avz --delete \
|
||||
--exclude '.git' \
|
||||
--exclude 'mobile/build' \
|
||||
--exclude 'mobile/.dart_tool' \
|
||||
--exclude 'backend/log' \
|
||||
--exclude 'backend/tmp' \
|
||||
--exclude 'backend/.bundle' \
|
||||
--exclude 'infra/.env' \
|
||||
--exclude 'node_modules' \
|
||||
"$SRC/" "$SERVER:$DEST/"
|
||||
else
|
||||
echo "rsync non disponibile sul server, uso tar+scp..."
|
||||
tar -C "$SRC" -czf /tmp/matchlivetv-deploy.tar.gz \
|
||||
--exclude='./mobile/build' --exclude='./mobile/.dart_tool' \
|
||||
--exclude='./backend/log' --exclude='./backend/tmp' --exclude='./infra/.env' --exclude='./.git' .
|
||||
scp /tmp/matchlivetv-deploy.tar.gz "$SERVER:~/matchlivetv-deploy.tar.gz"
|
||||
ssh "$SERVER" "mkdir -p $DEST && tar -xzf ~/matchlivetv-deploy.tar.gz -C $DEST"
|
||||
fi
|
||||
|
||||
echo "Done. On server run:"
|
||||
echo " cd $DEST/infra && cp .env.production.example .env # edit secrets"
|
||||
echo " docker compose -f docker-compose.prod.yml up -d --build"
|
||||
72
scripts/e2e_api.sh
Executable file
72
scripts/e2e_api.sh
Executable file
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
API="${API:-http://localhost:3000}"
|
||||
PLATFORM="${PLATFORM:-matchlivetv}"
|
||||
|
||||
echo "== Login =="
|
||||
LOGIN=$(curl -sf -X POST "$API/api/v1/auth/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"email":"coach@matchlivetv.test","password":"password123"}')
|
||||
TOKEN=$(echo "$LOGIN" | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
|
||||
echo "OK token received"
|
||||
|
||||
AUTH="Authorization: Bearer $TOKEN"
|
||||
|
||||
echo "== Teams =="
|
||||
TEAMS=$(curl -sf -H "$AUTH" "$API/api/v1/teams")
|
||||
TEAM_ID=$(echo "$TEAMS" | python3 -c "import sys,json; print(json.load(sys.stdin)[0]['id'])")
|
||||
echo "Team: $TEAM_ID"
|
||||
|
||||
echo "== Matches =="
|
||||
MATCHES=$(curl -sf -H "$AUTH" "$API/api/v1/teams/$TEAM_ID/matches")
|
||||
MATCH_ID=$(echo "$MATCHES" | python3 -c "import sys,json; print(json.load(sys.stdin)[0]['id'])")
|
||||
echo "Match: $MATCH_ID"
|
||||
|
||||
echo "== Create session (platform=$PLATFORM) =="
|
||||
SESSION=$(curl -sf -X POST -H "$AUTH" -H "Content-Type: application/json" \
|
||||
"$API/api/v1/matches/$MATCH_ID/sessions" \
|
||||
-d "{\"platform\":\"$PLATFORM\",\"privacy_status\":\"unlisted\",\"quality_preset\":\"720p_30_2.5mbps\"}")
|
||||
SESSION_ID=$(echo "$SESSION" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
|
||||
echo "Session: $SESSION_ID"
|
||||
|
||||
python3 -c "
|
||||
import sys, json
|
||||
s = json.loads('''$SESSION''')
|
||||
assert s.get('platform') == '$PLATFORM', s
|
||||
if '$PLATFORM' == 'matchlivetv':
|
||||
assert s.get('hls_playback_url'), 'missing hls_playback_url'
|
||||
assert s.get('watch_page_url'), 'missing watch_page_url'
|
||||
assert not s.get('youtube_broadcast_id'), s
|
||||
print('Session fields OK')
|
||||
"
|
||||
|
||||
echo "== Start session =="
|
||||
curl -sf -X PATCH -H "$AUTH" "$API/api/v1/sessions/$SESSION_ID/start" > /dev/null
|
||||
echo "Started"
|
||||
|
||||
echo "== Webhook disconnect =="
|
||||
SECRET="${MEDIAMTX_WEBHOOK_SECRET:-mediamtx_webhook_dev_secret}"
|
||||
BODY="{\"session_id\":\"$SESSION_ID\"}"
|
||||
SIG=$(echo -n "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')
|
||||
curl -sf -X POST "$API/webhooks/mediamtx/disconnect" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-MediaMTX-Signature: $SIG" \
|
||||
-d "$BODY"
|
||||
echo "Disconnect OK"
|
||||
|
||||
STATUS=$(curl -sf -H "$AUTH" "$API/api/v1/sessions/$SESSION_ID" | python3 -c "import sys,json; print(json.load(sys.stdin)['status'])")
|
||||
echo "Status after disconnect: $STATUS (expected reconnecting)"
|
||||
|
||||
echo "== Webhook connect =="
|
||||
curl -sf -X POST "$API/webhooks/mediamtx/connect" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-MediaMTX-Signature: $SIG" \
|
||||
-d "$BODY"
|
||||
STATUS=$(curl -sf -H "$AUTH" "$API/api/v1/sessions/$SESSION_ID" | python3 -c "import sys,json; print(json.load(sys.stdin)['status'])")
|
||||
echo "Status after reconnect: $STATUS (expected live)"
|
||||
|
||||
echo "== Validate publish token =="
|
||||
curl -sf -X POST "$API/internal/validate_publish" \
|
||||
-d "session_id=$SESSION_ID&token=fake" | grep -q '"valid":false' && echo "Invalid token rejected OK"
|
||||
|
||||
echo "== All E2E API checks passed =="
|
||||
14
scripts/run_mobile_android.sh
Executable file
14
scripts/run_mobile_android.sh
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
# Avvio su emulatore Android con port forwarding
|
||||
set -euo pipefail
|
||||
export PATH="$HOME/flutter/bin:$HOME/Android/Sdk/platform-tools:$PATH"
|
||||
export ANDROID_HOME="$HOME/Android/Sdk"
|
||||
export ANDROID_AVD_HOME="${ANDROID_AVD_HOME:-$HOME/.var/app/com.google.AndroidStudio/config/.android/avd}"
|
||||
|
||||
adb reverse tcp:3000 tcp:3000 2>/dev/null || true
|
||||
|
||||
cd "$(dirname "$0")/../mobile"
|
||||
flutter pub get
|
||||
flutter run -d emulator-5554 \
|
||||
--dart-define=API_BASE_URL=http://127.0.0.1:3000 \
|
||||
--dart-define=CABLE_URL=ws://127.0.0.1:3000/cable
|
||||
28
scripts/run_mobile_android_prod.sh
Executable file
28
scripts/run_mobile_android_prod.sh
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# Telefono Android fisico → API produzione
|
||||
set -euo pipefail
|
||||
export PATH="$HOME/flutter/bin:$HOME/Android/Sdk/platform-tools:$PATH"
|
||||
export ANDROID_HOME="$HOME/Android/Sdk"
|
||||
|
||||
DEVICE="${ANDROID_DEVICE:-}"
|
||||
if [[ -z "$DEVICE" ]]; then
|
||||
DEVICE=$(adb devices | awk '/device$/{print $1; exit}' | grep -v emulator || true)
|
||||
fi
|
||||
if [[ -z "$DEVICE" ]]; then
|
||||
echo "Nessun telefono USB collegato. Collega il device e abilita debug USB."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
API_BASE_URL="${API_BASE_URL:-https://matchlivetv.eminux.it}"
|
||||
MODE="${BUILD_MODE:-debug}"
|
||||
|
||||
cd "$(dirname "$0")/../mobile"
|
||||
flutter pub get
|
||||
|
||||
ARGS=(run -d "$DEVICE" "--dart-define=API_BASE_URL=$API_BASE_URL")
|
||||
if [[ "$MODE" == "release" ]]; then
|
||||
ARGS=(run --release -d "$DEVICE" "--dart-define=API_BASE_URL=$API_BASE_URL")
|
||||
fi
|
||||
|
||||
echo "Device: $DEVICE | API: $API_BASE_URL | Mode: $MODE"
|
||||
flutter "${ARGS[@]}"
|
||||
7
scripts/run_mobile_linux.sh
Executable file
7
scripts/run_mobile_linux.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
# Avvio leggero su Linux desktop (evita emulatore Android che consuma ~80% CPU)
|
||||
set -euo pipefail
|
||||
export PATH="$HOME/flutter/bin:$PATH"
|
||||
cd "$(dirname "$0")/../mobile"
|
||||
flutter pub get
|
||||
flutter run -d linux --dart-define=API_BASE_URL=http://127.0.0.1:3000
|
||||
Reference in New Issue
Block a user