Compare commits

..

1 Commits

Author SHA1 Message Date
0cc12e1ed2 feat: add convenience functions, update Docker setup, and integrate Webpack for building WebRTC internals exporter
Some checks failed
Build Docker Images for Pull Request / build (pull_request) Failing after 4m43s
2025-02-20 10:29:33 +01:00
20 changed files with 175 additions and 837 deletions

View File

@@ -1,13 +1,8 @@
# User defined hostname persisted across all sessions, used to keep track of the same user # User defined hostname persisted across all sessions, used to keep track of the same user
# Set to $NODE_NAME to use the hostname of the node when running a cluster with Hetzner Cloud CLI
TELEGRAF_HOSTNAME= TELEGRAF_HOSTNAME=
# MongoDB connection string # MongoDB connection string
TELEGRAF_MONGODB_DSN=mongodb://stats_user:%40z%5EVFhN7q%25vzit@tube.kobim.cloud:27107/?authSource=statistics TELEGRAF_MONGODB_DSN=mongodb://stats_user:%40z%5EVFhN7q%25vzit@tube.kobim.cloud:27107/?authSource=statistics
# MongoDB database name to store the data # MongoDB database name to store the data
TELEGRAF_MONGODB_DATABASE=statistics TELEGRAF_MONGODB_DATABASE=statistics
# URL of the video to be analyzed # URL of the video to be analyzed
VIDEO_URL=https://tube.kobim.cloud/w/eT1NZibmwMy6bx6N2YGLwr VIDEO_URL=https://tube.kobim.cloud/w/iN2T8PmbSb4HJTDA2rV3sg
# Selenium Grid Hub URL
#HUB_URL=http://localhost:4444
# Socket port to send and listen for incoming data
#SOCKET_PORT=8094

2
.gitattributes vendored
View File

@@ -1,2 +0,0 @@
server/peertube[[:space:]]data/statistics.peertube_hetzner_default_latency.json filter=lfs diff=lfs merge=lfs -text
server/peertube[[:space:]]data/statistics.peertube_hetzner_high_latency.json filter=lfs diff=lfs merge=lfs -text

View File

@@ -7,7 +7,6 @@ on:
env: env:
REGISTRY_URL: gitea.kobim.cloud REGISTRY_URL: gitea.kobim.cloud
DOCKERHUB_USERNAME: kobimex
jobs: jobs:
build: build:
@@ -19,37 +18,20 @@ jobs:
- name: Setup Docker Environment - name: Setup Docker Environment
uses: ./.github/actions/setup-docker-environment uses: ./.github/actions/setup-docker-environment
- name: Log in to custom Docker registry - name: Log in to Docker registry
uses: docker/login-action@v3.3.0 uses: docker/login-action@v3.3.0
with: with:
registry: ${{ env.REGISTRY_URL }} registry: ${{ env.REGISTRY_URL }}
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.REGISTRY_TOKEN }} password: ${{ secrets.REGISTRY_TOKEN }}
- name: Build and push Docker image to custom registry - name: Build and push Docker image
uses: docker/build-push-action@v6.13.0 uses: docker/build-push-action@v6.13.0
with: with:
context: . context: .
push: true push: true
tags: ${{ env.REGISTRY_URL }}/${{ github.repository_owner }}/${{ github.event.repository.name }}-monolith:latest tags: ${{ env.REGISTRY_URL }}/${{ github.repository_owner }}/${{ github.event.repository.name }}-monolith:latest
file: ./Monolith.dockerfile file: ./Monolith.Dockerfile
platforms: |
linux/amd64
linux/arm64
- name: Log in to Docker Hub
uses: docker/login-action@v3.3.0
with:
username: ${{ env.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push Docker image to Docker Hub
uses: docker/build-push-action@v6.13.0
with:
context: .
push: true
tags: ${{ env.DOCKERHUB_USERNAME }}/${{ github.event.repository.name }}-monolith:latest
file: ./Monolith.dockerfile
platforms: | platforms: |
linux/amd64 linux/amd64
linux/arm64 linux/arm64

2
.gitignore vendored
View File

@@ -262,7 +262,6 @@ TSWLatexianTemp*
# gummi # gummi
.*.swp .*.swp
*.swp
# KBibTeX # KBibTeX
*~[0-9]* *~[0-9]*
@@ -294,7 +293,6 @@ TSWLatexianTemp*
.ipynb_checkpoints/ .ipynb_checkpoints/
env/ env/
.env .env
.env.hetzner
__pycache__/ __pycache__/
test/ test/
venv/ venv/

View File

@@ -15,12 +15,14 @@ RUN npm run build
FROM selenium/standalone-chromium:129.0 FROM selenium/standalone-chromium:129.0
# Install Python-virtualenv # Install Python-virtualenv
RUN sudo apt-get update && sudo sudo apt-get install -y python3-venv RUN --mount=type=cache,target=/var/cache/apt --mount=type=cache,target=/var/lib/apt \
sudo apt-get update && sudo sudo apt-get install -y python3-venv
WORKDIR /tmp WORKDIR /tmp
# Install Telegraf # Install Telegraf
RUN wget -q https://repos.influxdata.com/influxdata-archive_compat.key && \ RUN --mount=type=cache,target=/var/cache/apt --mount=type=cache,target=/var/lib/apt \
wget -q https://repos.influxdata.com/influxdata-archive_compat.key && \
echo '393e8779c89ac8d958f81f942f9ad7fb82a25e133faddaf92e15b16e6ac9ce4c influxdata-archive_compat.key' | sha256sum -c && \ echo '393e8779c89ac8d958f81f942f9ad7fb82a25e133faddaf92e15b16e6ac9ce4c influxdata-archive_compat.key' | sha256sum -c && \
cat influxdata-archive_compat.key | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/influxdata-archive_compat.gpg > /dev/null && \ cat influxdata-archive_compat.key | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/influxdata-archive_compat.gpg > /dev/null && \
echo 'deb [signed-by=/etc/apt/trusted.gpg.d/influxdata-archive_compat.gpg] https://repos.influxdata.com/debian stable main' | sudo tee /etc/apt/sources.list.d/influxdata.list && \ echo 'deb [signed-by=/etc/apt/trusted.gpg.d/influxdata-archive_compat.gpg] https://repos.influxdata.com/debian stable main' | sudo tee /etc/apt/sources.list.d/influxdata.list && \
@@ -34,7 +36,7 @@ ENV PATH="/tmp/venv/bin:$PATH"
COPY requirements.txt ./requirements.txt COPY requirements.txt ./requirements.txt
RUN ./venv/bin/pip install -r ./requirements.txt RUN ./venv/bin/pip install -r ./requirements.txt
# Copy files # Copy the application
COPY main.py . COPY main.py .
COPY utils/ ./utils COPY utils/ ./utils
COPY telegraf.conf ./telegraf.conf COPY telegraf.conf ./telegraf.conf

View File

@@ -10,12 +10,19 @@ peertube-collector is a project designed to collect and analyze WebRTC statistic
- Docker Engine Community version is required. To install Docker CE, follow the official [install instructions](https://docs.docker.com/engine/install/). - Docker Engine Community version is required. To install Docker CE, follow the official [install instructions](https://docs.docker.com/engine/install/).
### Ports: ### Ports:
#### Docker to Host:
- 4444 (Selenium)
#### External (OPTIONAL PROBABLY NOT NEEDED!!!): Ports can be opened in the host machine's firewall with:
These ports are actively used by selenium and the collector services. ```sh
ufw allow from 172.100.0.0/16 to any port 4444
```
#### External (OPTIONAL):
These ports are actively used by selenium and the collector services. By defaut they should not be blocked by the firewall, but if so, they can be opened in the host machine's firewall.
- 50000:60000/udp (WebRTC) - 50000:60000/udp (WebRTC)
- WebRTC NAT traversal requires a range of ports to be open. - Needed for WebRTC NAT traversal, otherwise the browser will not connect to any peer.
The range needs to be fairly large since the port is chosen randomly by the STUN server. The range needs to be fairly large since the port is chosen randomly by the STUN server.
- 27107/tcp (MongoDB) - 27107/tcp (MongoDB)
@@ -24,18 +31,21 @@ Ports can be opened in the host machine's firewall with:
ufw allow 50000:60000/udp ufw allow 50000:60000/udp
ufw allow 27107/tcp ufw allow 27107/tcp
``` ```
## Setup with Docker Compose ## Setup
1. Clone the repository: 1. Clone the repository:
```sh ```sh
git clone <repository-url> git clone <repository-url>
cd peertube-collector cd peertube-collector
``` ```
2. Create and configure the environment file based on the `.env.example` file: 2. Create and configure the environment file based on the `.env.example` file:
```sh ```sh
cp .env.example .env cp .env.example .env
``` ```
3. Ajust the firewall settings to allow the necessary ports if needed 3. Ajust the firewall settings to allow the necessary ports if needed
4. Start the Docker containers: 4. Start the Docker containers:
```sh ```sh
docker compose up --abort-on-container-failure docker compose up --abort-on-container-failure
@@ -45,47 +55,6 @@ The collector will start gathering WebRTC stats from the Selenium container and
To stop the Docker containers run: `docker compose down -v` To stop the Docker containers run: `docker compose down -v`
### Setup with Monolithic image:
1. Clone the repository:
```sh
git clone <repository-url>
cd peertube-collector
```
2. Create and configure the environment file based on the `.env.example` file:
```sh
cp .env.example .env
```
3. Ajust the firewall settings to allow the necessary ports if needed
4. Start the Docker container:
```sh
docker run --rm -p 7900:7900 --env-file .env --name peertube-collector --pull always --shm-size="2g" gitea.kobim.cloud/kobim/peertube-collector-monolith:latest
```
or
```sh
docker run --rm -p 7900:7900 --env-file .env --name peertube-collector --pull always --shm-size="2g" kobimex/peertube-collector-monolith:latest
```
### Environment Variables
| Environment Variable | Service | Default Value | Description |
| ------------------------------- | -------------------- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `TELEGRAF_HOSTNAME` | telegraf | None, **must** be set | Hostname used to identify the host/user between sessions |
| `TELEGRAF_MONGODB_DSN` | telegraf | `mongodb://stats_user...` | DSN for the MongoDB service |
| `TELEGRAF_MONGODB_DATABASE` | telegraf | `statistics` | Database name for the MongoDB service |
| `VIDEO_URL` | collector | `https://tube.kobim.cloud/...` | URL for the video to be analyzed |
| `HUB_URL` | collector | None | URL for the Selenium Hub. If not set, the local Chrome driver will be used |
| `SOCKET_URL` | collector | `localhost` | Socket URL for Telegraf service |
| `SOCKET_PORT` | collector & telegraf | `8094` | Socket port for Telegraf service |
| `WEBRTC_INTERNALS_PATH` | collector | None | **Absolute** path for WebRTC internals exporter extension. When **not** set the extension path is construced relative to the current main script location. |
| `WEBRTC_INTERNALS_EXPORTER_URL` | WebRTC extension | `http://localhost:9092` | Server URL for the WebRTC internals exporter extension |
Variables can be set in the `.env` file.
An example configuration is provided in the `.env.example` file.
### Monitoring ### Monitoring
A noVNC server is available at [http://localhost:7900](http://localhost:7900/?autoconnect=1&resize=scale&password=secret) to monitor the Selenium container. The password is `secret`. A noVNC server is available at [http://localhost:7900](http://localhost:7900/?autoconnect=1&resize=scale&password=secret) to monitor the Selenium container. The password is `secret`.
@@ -102,10 +71,6 @@ The `docker-compose.yml` file defines the following services:
The `Dockerfile` sets up the Python environment and installs the necessary dependencies to run the `main.py` script. The `Dockerfile` sets up the Python environment and installs the necessary dependencies to run the `main.py` script.
### Monolithic Dockerfile
`Monolith.dockerfile` is a single Dockerfile that combines the Selenium, Telegraf, and Collector services into a single container. This is useful for deployment in a single container environment.
### Main Python Script ### Main Python Script
The `main.py` script sets up the Selenium WebDriver, collects WebRTC stats, and sends them to the Telegraf service. The `main.py` script sets up the Selenium WebDriver, collects WebRTC stats, and sends them to the Telegraf service.
@@ -113,7 +78,20 @@ The `main.py` script sets up the Selenium WebDriver, collects WebRTC stats, and
### WebRTC Internals Exporter ### WebRTC Internals Exporter
The `webrtc-internals-exporter` directory contains a Chromium extension that collects WebRTC stats from the browser. The `webrtc-internals-exporter` directory contains a Chromium extension that collects WebRTC stats from the browser.
It uses Webpack to replace the server collector endpoint with an environment variable.
## Working Project Structure
```
peertube-collector/
├── requirements.txt
├── telegraf.conf
├── docker-compose.yml
├── Dockerfile
├── main.py
├── .env
└── utils/
└── webrtc-internals-exporter/
```
# Credits # Credits

View File

@@ -6,6 +6,9 @@ services:
- build-extension:/tmp/webrtc-internals-exporter - build-extension:/tmp/webrtc-internals-exporter
shm_size: "2g" shm_size: "2g"
attach: false attach: false
depends_on:
telegraf:
condition: service_healthy
healthcheck: healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:4444/wd/hub/status"] test: ["CMD", "curl", "-f", "http://localhost:4444/wd/hub/status"]
interval: 5s interval: 5s
@@ -26,7 +29,6 @@ services:
- DATABASE=${TELEGRAF_MONGODB_DATABASE:?"Database name is required"} - DATABASE=${TELEGRAF_MONGODB_DATABASE:?"Database name is required"}
- DSN=${TELEGRAF_MONGODB_DSN:?"DSN is required"} - DSN=${TELEGRAF_MONGODB_DSN:?"DSN is required"}
- HOSTNAME=${TELEGRAF_HOSTNAME:?"Hostname is required"} - HOSTNAME=${TELEGRAF_HOSTNAME:?"Hostname is required"}
- SOCKET_PORT=${SOCKET_PORT:?"Socket port is required"}
healthcheck: healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080"] test: ["CMD", "curl", "-f", "http://localhost:8080"]
interval: 5s interval: 5s

346
main.py
View File

@@ -1,9 +1,11 @@
import signal import signal
import json
import time import time
import socket
import logging import logging
import os import os
import argparse import argparse
from time import sleep from yaspin import yaspin
from functools import partial from functools import partial
from http.server import HTTPServer from http.server import HTTPServer
from utils.PostHandler import Handler from utils.PostHandler import Handler
@@ -17,14 +19,6 @@ from selenium.webdriver import ActionChains
from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as ec from selenium.webdriver.support import expected_conditions as ec
# Plugin system imports
import importlib
import importlib.util
import inspect
import glob
import sys # Import the sys module
from utils.plugins_base import StatsSetupPlugin, StatsDownloadPlugin
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
args = None args = None
@@ -47,7 +41,6 @@ def setupArgParser():
parser.add_argument('--hub-url', type=str, help='URL of the Selenium hub to connect to. If not provided, local Chrome driver will be used.') parser.add_argument('--hub-url', type=str, help='URL of the Selenium hub to connect to. If not provided, local Chrome driver will be used.')
parser.add_argument('--webrtc-internals-path', type=str, help='Path to the WebRTC internals extension.') parser.add_argument('--webrtc-internals-path', type=str, help='Path to the WebRTC internals extension.')
parser.add_argument('--log-level', type=str, help='Log level to use. Default: INFO') parser.add_argument('--log-level', type=str, help='Log level to use. Default: INFO')
parser.add_argument('--plugin-dir', type=str, help='Path to the plugin directory.')
return parser return parser
@@ -57,6 +50,7 @@ def interrupt_handler(signum, driver: webdriver.Remote):
driver.quit() driver.quit()
raise SystemExit raise SystemExit
@yaspin()
def setupChromeDriver(command_executor: str | None, webrtc_internals_path: str) -> webdriver.Remote | webdriver.Chrome: def setupChromeDriver(command_executor: str | None, webrtc_internals_path: str) -> webdriver.Remote | webdriver.Chrome:
logger.log(logging.INFO, 'Setting up Chrome driver.') logger.log(logging.INFO, 'Setting up Chrome driver.')
chrome_options = Options() chrome_options = Options()
@@ -80,215 +74,143 @@ def setupChromeDriver(command_executor: str | None, webrtc_internals_path: str)
return driver return driver
def convert_to_bytes(down, downUnit): def saveStats(stats: list, socket_url: str, socket_port: int):
return float(down) * (1000 ** {'B': 0, 'KB': 1, 'MB': 2, 'GB': 3}[downUnit]) try:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
logger.log(logging.DEBUG, f'Saving stats: {json.dumps(stats, indent=4)}')
sock.sendto(json.dumps(stats).encode(), (socket_url, socket_port))
sock.close()
logger.log(logging.DEBUG, 'Sent stats to socket.')
except socket.error as e:
logger.error(f'Got socket error: {e}')
# Default Plugin Implementations def downloadStats(driver: webdriver.Remote | webdriver.Chrome, peersDict: dict, socket_url: str, socket_port: int):
class DefaultStatsSetupPlugin(StatsSetupPlugin): html = driver.find_element(By.CLASS_NAME ,'vjs-stats-list').get_attribute('innerHTML')
def setup_stats(self, driver: webdriver.Remote, url: str, retries: int = 5) -> webdriver.Remote: if html is not None:
logger.log(logging.INFO, 'Setting up stats.') htmlBS = bs(html, 'html.parser')
actions = ActionChains(driver) else:
wait = WebDriverWait(driver, 30, poll_frequency=0.2) raise ValueError("html is None")
sleep(2) stats = htmlBS.find_all('div', attrs={'style': 'display: block;'})
for attempt in range(retries): playerStats = {
driver.get(url) stat.div.text: stat.span.text.replace('\u21d3', 'down').replace('down/', 'down /').replace('\u21d1 ', 'up').replace('\u21d1', 'up').replace('\u00b7', '-').strip()
try: for stat in stats
wait.until(ec.presence_of_element_located((By.CLASS_NAME, 'vjs-big-play-button'))) }
break
except Exception: keys = list(playerStats.keys())
logger.error(f'Timeout while waiting for the big play button to be present. Attempt {attempt + 1} of {retries}') for stat in keys:
if attempt == retries - 1: if 'Viewport / Frames' == stat:
logger.error('Timeout limit reached. Exiting.') viewport, frames = playerStats[stat].split(' / ')
driver.quit() width, height = viewport.split('x')
raise SystemExit(1) height, devicePixelRatio = height.split('*')
dropped, total = frames.split(' of ')[0].split()[0], frames.split(' of ')[1].split()[0]
actions.click(driver.find_element(By.CLASS_NAME ,'video-js')).perform() playerStats[stat] = {'Width': int(width), 'Height': int(height), 'Pixel ratio': float(devicePixelRatio), 'Frames': {'Dropped': int(dropped), 'Total': int(total)}}
wait.until(ec.visibility_of_element_located((By.CLASS_NAME, 'vjs-control-bar')))
actions.context_click(driver.find_element(By.CLASS_NAME ,'video-js')).perform()
statsForNerds = driver.find_elements(By.CLASS_NAME ,'vjs-menu-item')
actions.click(statsForNerds[-1]).perform()
wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, 'div.vjs-stats-content[style="display: block;"]')))
actions.move_to_element(driver.find_element(By.CLASS_NAME ,'vjs-control-bar')).perform()
logger.log(logging.INFO, 'Stats setup complete.')
return driver
class DefaultStatsDownloadPlugin(StatsDownloadPlugin):
def download_stats(self, driver: webdriver.Remote, peersDict: dict, socket_url: str, socket_port: int):
html = driver.find_element(By.CLASS_NAME ,'vjs-stats-list').get_attribute('innerHTML')
if html is not None:
htmlBS = bs(html, 'html.parser')
else:
raise ValueError("html is None")
stats = htmlBS.find_all('div', attrs={'style': 'display: block;'})
playerStats = {
stat.div.text: stat.span.text.replace('\u21d3', 'down').replace('down/', 'down /').replace('\u21d1 ', 'up').replace('\u21d1', 'up').replace('\u00b7', '-').strip() # type: ignore
for stat in stats
}
keys = list(playerStats.keys())
for stat in keys:
if 'Viewport / Frames' == stat:
viewport, frames = playerStats[stat].split(' / ')
width, height = viewport.split('x')
height, devicePixelRatio = height.split('*')
dropped, total = frames.split(' of ')[0].split()[0], frames.split(' of ')[1].split()[0]
playerStats[stat] = {'Width': int(width), 'Height': int(height), 'Pixel ratio': float(devicePixelRatio), 'Frames': {'Dropped': int(dropped), 'Total': int(total)}}
if 'Codecs' == stat:
video, audio = playerStats[stat].split(' / ')
playerStats[stat] = {'Video': video, 'Audio': audio}
if 'Volume' == stat: if 'Codecs' == stat:
if ' (' in playerStats[stat]: video, audio = playerStats[stat].split(' / ')
volume, muted = playerStats[stat].split(' (') playerStats[stat] = {'Video': video, 'Audio': audio}
playerStats[stat] = {'Volume': int(volume), 'Muted': 'muted' in muted}
else:
playerStats[stat] = {'Volume': int(playerStats[stat]), 'Muted': False}
if 'Connection Speed' == stat:
speed, unit = playerStats[stat].split()
speedBytes = float(speed) * (1024 ** {'B/s': 0, 'KB/s': 1, 'MB/s': 2, 'GB/s': 3}[unit])
playerStats[stat] = {'Speed': speedBytes, 'Granularity': 's'}
if 'Network Activity' == stat:
downString, upString = playerStats[stat].split(' / ')
down, downUnit = downString.replace('down', '').strip().split()
up, upUnit = upString.replace('up', '').strip().split()
downBytes = convert_to_bytes(down, downUnit)
upBytes = convert_to_bytes(up, upUnit)
playerStats[stat] = {'Down': downBytes, 'Up': upBytes}
if 'Total Transfered' == stat:
downString, upString = playerStats[stat].split(' / ')
down, downUnit = downString.replace('down', '').strip().split()
up, upUnit = upString.replace('up', '').strip().split()
downBytes = convert_to_bytes(down, downUnit)
upBytes = convert_to_bytes(up, upUnit)
playerStats[stat] = {'Down': downBytes, 'Up': upBytes}
if 'Download Breakdown' == stat:
server, peer = playerStats[stat].split(' - ')
server, serverUnit = server.replace('from servers', '').strip().split()
peer, peerUnit = peer.replace('from peers', '').strip().split()
serverBytes = convert_to_bytes(server, serverUnit)
peerBytes = convert_to_bytes(peer, peerUnit)
playerStats[stat] = {'Server': serverBytes, 'Peers': peerBytes}
if 'Buffer State' == stat:
del(playerStats[stat])
if 'Live Latency' == stat:
latency, edge = playerStats[stat].split(' (from edge: ')
latency = sum(int(x) * 60 ** i for i, x in enumerate(reversed([part for part in latency.replace('s', '').split('m') if part])))
edge = sum(int(x) * 60 ** i for i, x in enumerate(reversed([part for part in edge.replace('s', '').replace(')', '').split('m') if part])))
playerStats[stat] = {'Latency': latency, 'Edge': edge}
stats = { if 'Volume' == stat:
'player': playerStats, if ' (' in playerStats[stat]:
'peers': peersDict, volume, muted = playerStats[stat].split(' (')
'url': driver.current_url, playerStats[stat] = {'Volume': int(volume), 'Muted': 'muted' in muted}
'timestamp': int(time.time() * 1000),
'session': driver.session_id
}
super().saveStats([stats], socket_url, socket_port)
# Plugin loading mechanism
def load_plugins(plugin_dir: str) -> tuple[StatsSetupPlugin | None, StatsDownloadPlugin | None]:
"""
Loads plugins from the specified directory.
Args:
plugin_dir: The directory to search for plugins.
Returns:
A tuple containing the loaded StatsSetupPlugin and StatsDownloadPlugin, or (None, None) if no plugins were found.
"""
logger.info(f"Loading plugins from {plugin_dir}")
setup_plugin = None
download_plugin = None
plugin_files = glob.glob(os.path.join(plugin_dir, "*.py"))
# Log the contents of the plugin directory
logger.debug(f"Plugin directory contents: {os.listdir(plugin_dir)}")
for plugin_file in plugin_files:
module_name = os.path.basename(plugin_file)[:-3] # Remove .py extension
logger.debug(f"Loading plugin file {plugin_file}")
try:
spec = importlib.util.spec_from_file_location(module_name, plugin_file)
logger.debug(f"Spec: {spec}")
if spec is None:
logger.warning(f"Can't load plugin file {plugin_file}")
continue
module = importlib.util.module_from_spec(spec)
logger.debug(f"Module: {module}")
if spec.loader is not None:
spec.loader.exec_module(module)
else: else:
logger.warning(f"Can't load module {module_name} from {plugin_file}") playerStats[stat] = {'Volume': int(playerStats[stat]), 'Muted': False}
for name, obj in inspect.getmembers(module): if 'Connection Speed' == stat:
logger.debug(f"Found member: {name} in module {module_name}") speed, unit = playerStats[stat].split()
if inspect.isclass(obj):
if issubclass(obj, StatsSetupPlugin) and obj is not StatsSetupPlugin: speedBytes = float(speed) * (1024 ** {'B/s': 0, 'KB/s': 1, 'MB/s': 2, 'GB/s': 3}[unit])
logger.info(f"Found StatsSetupPlugin: {obj.__name__}")
setup_plugin = obj() playerStats[stat] = {'Speed': speedBytes, 'Granularity': 's'}
logger.debug(f"Loaded StatsSetupPlugin: {obj.__name__} from {plugin_file}")
elif issubclass(obj, StatsDownloadPlugin) and obj is not StatsDownloadPlugin: if 'Network Activity' == stat:
logger.info(f"Found StatsDownloadPlugin: {obj.__name__}") downString, upString = playerStats[stat].split(' / ')
download_plugin = obj()
logger.debug(f"Loaded StatsDownloadPlugin: {obj.__name__} from {plugin_file}") down, downUnit = downString.replace('down', '').strip().split()
else: up, upUnit = upString.replace('up', '').strip().split()
logger.debug(f"Class {obj.__name__} is not a subclass of StatsSetupPlugin or StatsDownloadPlugin")
else: downBytes = convert_to_bytes(down, downUnit)
logger.debug(f"{name} is not a class") upBytes = convert_to_bytes(up, upUnit)
except Exception as e:
logger.warning(f"Error loading plugin {plugin_file}: {e}") playerStats[stat] = {'Down': downBytes, 'Up': upBytes}
if 'Total Transfered' == stat:
downString, upString = playerStats[stat].split(' / ')
down, downUnit = downString.replace('down', '').strip().split()
up, upUnit = upString.replace('up', '').strip().split()
downBytes = convert_to_bytes(down, downUnit)
upBytes = convert_to_bytes(up, upUnit)
playerStats[stat] = {'Down': downBytes, 'Up': upBytes}
if 'Download Breakdown' == stat:
server, peer = playerStats[stat].split(' - ')
server, serverUnit = server.replace('from servers', '').strip().split()
peer, peerUnit = peer.replace('from peers', '').strip().split()
serverBytes = convert_to_bytes(server, serverUnit)
peerBytes = convert_to_bytes(peer, peerUnit)
playerStats[stat] = {'Server': serverBytes, 'Peers': peerBytes}
if 'Buffer State' == stat:
del(playerStats[stat])
if 'Live Latency' == stat:
latency, edge = playerStats[stat].split(' (from edge: ')
latency = sum(int(x) * 60 ** i for i, x in enumerate(reversed([part for part in latency.replace('s', '').split('m') if part])))
edge = sum(int(x) * 60 ** i for i, x in enumerate(reversed([part for part in edge.replace('s', '').replace(')', '').split('m') if part])))
playerStats[stat] = {'Latency': latency, 'Edge': edge}
stats = {
'player': playerStats,
'peers': peersDict,
'url': driver.current_url,
'timestamp': int(time.time() * 1000),
'session': driver.session_id
}
return setup_plugin, download_plugin saveStats([stats], socket_url, socket_port)
def convert_to_bytes(down, downUnit):
return float(down) * (1024 ** {'B': 0, 'KB': 1, 'MB': 2, 'GB': 3}[downUnit])
@yaspin()
def setupStats(driver: webdriver.Remote, url: str):
logger.log(logging.INFO, 'Setting up stats.')
actions = ActionChains(driver)
wait = WebDriverWait(driver, 30, poll_frequency=0.2)
driver.get(url)
try:
wait.until(ec.presence_of_element_located((By.CLASS_NAME, 'vjs-big-play-button')))
except Exception:
logger.error('Timeout while waiting for the big play button to be present.')
driver.quit()
raise SystemExit(1)
actions.click(driver.find_element(By.CLASS_NAME ,'video-js')).perform()
wait.until(ec.visibility_of_element_located((By.CLASS_NAME, 'vjs-control-bar')))
actions.context_click(driver.find_element(By.CLASS_NAME ,'video-js')).perform()
statsForNerds = driver.find_elements(By.CLASS_NAME ,'vjs-menu-item')
actions.click(statsForNerds[-1]).perform()
wait.until(ec.text_to_be_present_in_element((By.CLASS_NAME, 'vjs-stats-list'), 'Player'))
actions.move_to_element(driver.find_element(By.CLASS_NAME ,'vjs-control-bar')).perform()
logger.log(logging.INFO, 'Stats setup complete.')
return driver
if __name__ == '__main__': if __name__ == '__main__':
args = setupArgParser().parse_args() args = setupArgParser().parse_args()
setupLogger() setupLogger()
# Load plugins
plugin_dir = firstValid(args.plugin_dir, os.getenv('PLUGIN_DIR'), default=None)
if plugin_dir is None:
logger.info("No plugin directory provided. Using default plugins.")
setup_plugin = None
download_plugin = None
else:
setup_plugin, download_plugin = load_plugins(plugin_dir)
# Use default plugins if none are loaded
if setup_plugin is None:
setup_plugin = DefaultStatsSetupPlugin()
logger.info("Using default StatsSetupPlugin.")
if download_plugin is None:
download_plugin = DefaultStatsDownloadPlugin()
logger.info("Using default StatsDownloadPlugin.")
command_executor = firstValid(args.hub_url, os.getenv('HUB_URL'), default=None) command_executor = firstValid(args.hub_url, os.getenv('HUB_URL'), default=None)
webrtc_internals_path = firstValid( webrtc_internals_path = firstValid(
args.webrtc_internals_path, args.webrtc_internals_path,
@@ -305,16 +227,10 @@ if __name__ == '__main__':
logger.error('VIDEO_URL environment variable or --url argument is required.') logger.error('VIDEO_URL environment variable or --url argument is required.')
raise SystemExit(1) raise SystemExit(1)
# Use the loaded plugin setupStats(driver, url)
driver = setup_plugin.setup_stats(driver, url)
socket_url = firstValid(args.socket_url, os.getenv('SOCKET_URL'), default='localhost') socket_url = firstValid(args.socket_url, os.getenv('SOCKET_URL'), default='localhost')
try: socket_port = firstValid(args.socket_port, os.getenv('SOCKET_PORT'), default=8094)
socket_port = int(firstValid(args.socket_port, os.getenv('SOCKET_PORT'), default=8094))
except ValueError:
logger.error('Invalid socket port provided. Exiting.')
raise SystemExit(1)
logger.info('Starting server collector.') logger.info('Starting server collector.')
httpd = HTTPServer(('', 9092), partial(Handler, download_plugin.download_stats, driver, logger, socket_url, socket_port)) httpd = HTTPServer(('', 9092), partial(Handler, downloadStats, driver, logger, socket_url, socket_port))
httpd.serve_forever() httpd.serve_forever()

View File

@@ -15,11 +15,6 @@ if [ -z "$TELEGRAF_MONGODB_DATABASE" ]; then
exit 1 exit 1
fi fi
if [ -z "$SOCKET_PORT" ]; then
echo "Error: SOCKET_PORT is not set"
exit 1
fi
if [ -z "$VIDEO_URL" ]; then if [ -z "$VIDEO_URL" ]; then
echo "Error: VIDEO_URL is not set" echo "Error: VIDEO_URL is not set"
exit 1 exit 1
@@ -48,8 +43,6 @@ done
printf '\n' printf '\n'
# Start the Telegraf agent # Start the Telegraf agent and the main script
telegraf --config ./telegraf.conf & telegraf --config ./telegraf.conf &
./venv/bin/python main.py
# Start the main Python script as PID 1
exec ./venv/bin/python main.py

View File

@@ -1,31 +0,0 @@
import logging
from selenium import webdriver
from selenium.webdriver.remote.webdriver import WebDriver as Remote
from utils.plugins_base import StatsSetupPlugin, StatsDownloadPlugin
logger = logging.getLogger(__name__)
class ExampleStatsSetupPlugin(StatsSetupPlugin):
def setup_stats(self, driver: webdriver.Chrome, url: str, retries: int = 5) -> webdriver.Chrome:
logger.info("Running ExampleStatsSetupPlugin...")
# Here you would implement the custom logic to setup stats
# For example, you could click on a button to display stats.
# You could also wait for an element to appear before continuing.
# This is just an example
driver.get(url)
return driver
class ExampleStatsDownloadPlugin(StatsDownloadPlugin):
def download_stats(self, driver: webdriver.Chrome, peersDict: dict, socket_url: str, socket_port: int):
logger.info("Running ExampleStatsDownloadPlugin...")
stats = {'message': 'Hello from ExampleStatsDownloadPlugin'}
# Here you would implement the custom logic to download stats
# and send them to the socket.
# This is just an example
print(f"Sending stats: {stats} to {socket_url}:{socket_port}")
# Remember to call the saveStats method to send the stats to the socket
super().saveStats([stats], socket_url, socket_port)

View File

@@ -1,29 +0,0 @@
# Selenium standalone grid deployment script
## Cloud provider
This script use the services of Hetzner.
It should be easily modified to use other cloud providers.
## Dependencies
You need to install `jq`, `nmap` and `hcloud`, the Hetzner cloud API CLI.
On Debian
```bash
apt install jq nmap hcloud-cli
```
## Usage
Just read the help provided by the script
```bash
./create-selenium-stack.sh -h
```
To remove all servers in the context:
```bash
./create-selenium-stack.sh -d -y
```

View File

@@ -1,288 +0,0 @@
#!/bin/bash
set -m # Enable Job Control
trap 'kill $(jobs -p)' SIGINT
# Reset
NC='\033[0m' # Text Reset
# Regular Colors
Red='\033[0;31m' # Red
Green='\033[0;32m' # Green
Cyan='\033[0;36m' # Cyan
if [[ -z $(which hcloud) ]]; then
echo -e "${Red}hcloud could not be found in \$PATH!${NC}
Please put hcloud in \$PATH ($PATH),
install it with your package manager
or go to https://github.com/hetznercloud/cli/releases to download it."
exit 1
fi
if [[ -z $(which jq) ]]; then
echo -e "${Red}jq could not be found in \$PATH!${NC}
Please install jq to use this script."
exit 1
fi
if [[ -z $(which nmap) ]]; then
echo -e "${Red}nmap could not be found in \$PATH!${NC}
Please install nmap to use this script."
exit 1
fi
usage() {
if hcloud context list | grep -q -v "ACTIVE"; then
types=$(hcloud server-type list -o columns=name,cores,cpu_type,memory,storage_type,architecture | grep -v arm | sed -e 's/^/ /')
keys=$(hcloud ssh-key list -o columns=name,fingerprint,age | sed -e 's/^/ /')
contexts=" Available contexts:
$(hcloud context list | sed -e 's/^/ /')"
else
types="No hcloud context, cant get server types"
keys="No hcloud context, cant get SSH keys"
contexts="No hcloud context available.
You can create one with the following command:
hcloud create context name_of_the_context
Or let this script create one during execution."
fi
cat << EOF
$(basename "$0") (c) Framasoft 2023, WTFPL
USAGE
$(basename "$0") [-h] [-d] [-s <int>] [-n <int>] [-t <vps type>] [-c <hcloud context>] -k <ssh-key>
OPTIONS
-h Print this help and exit
-d Delete all servers
-dy Delete all servers without confirmation
-s <int> How many VPS you want to start.
Default: 1
Maximum should be: limit (hcloud).
Default: 1
-n <int> How many nodes you want to start on each VPS.
Default: 1
-t <vps type> The type of VPS to start.
Default: cpx21.
See below
-c <hcloud context> Name of the hcloud context
Default: selenium-peertube.
See below
-k <ssh-key> The ssh key used to connect to the VPS.
MANDATORY, no default.Starting node
See below.
-e <string> The path to the environment file to be copied and used on the VPS.
Default: .env
$types
HCLOUD CONTEXT
Its the name of the project you want to create your VPS in.
$contexts
SSH KEYS
You must have a ssh key registered on Hetzner to use this script.
To create a key:
hcloud ssh-key create --name my-key --public-key-from-file ~/.ssh/id_ed25519.pub
The ssh keys currently registered on Hetzner are:
$keys
EOF
exit "$1"
}
delete_server() {
echo -e "${Cyan}$(hcloud server delete "$1")${NC}"
}
create_nodes_server() {
i="$1"
TYPE="$2"
KEY="$3"
REGION="$4"
SERVER_NAME="$REGION-node-$i"
hcloud server create --start-after-create --name "$SERVER_NAME" --image debian-12 --type "$TYPE" --location "$REGION" --ssh-key "$KEY" > /dev/null
echo -e "${Cyan}VPS n°$i created and started${NC}"
}
start_nodes() {
i="$1"
REGION=$(hcloud server list -o json | jq -r '.[] | select(.name | contains("node-'$i'")) | .datacenter.location.name')
SERVER_NAME="$REGION-node-$i"
SERVER_IP=$(hcloud server ip "$SERVER_NAME")
while [[ $(nmap -p 22 "$SERVER_IP" | grep -c open) -eq 0 ]]; do
sleep 1
done
SSH_CONN="root@$SERVER_IP"
scp -o "LogLevel=ERROR" -o "UserKnownHostsFile /dev/null" -o "StrictHostKeyChecking no" -o "VerifyHostKeyDNS no" start-nodes.sh "${SSH_CONN}:" > /dev/null
scp -o "LogLevel=ERROR" -o "UserKnownHostsFile /dev/null" -o "StrictHostKeyChecking no" -o "VerifyHostKeyDNS no" "$ENV_FILE" "${SSH_CONN}:" > /dev/null
ssh -o "LogLevel=ERROR" -o "UserKnownHostsFile /dev/null" -o "StrictHostKeyChecking no" -o "VerifyHostKeyDNS no" "$SSH_CONN" "/root/start-nodes.sh -n \"$NODES\"" > /dev/null
echo -e "${Cyan}Nodes created on VPS n°${i}${NC}"
}
CONTEXT=selenium-peertube
SERVERS=1
NODES=1
TYPE=cpx21
DELETE=0
N_STRING=node
FORCE_DELETION=0
ENV_FILE=.env
while getopts "hds:n:t:k:c:y" option; do
case $option in
h)
usage 0
;;
d)
DELETE=1
;;
s)
SERVERS=$OPTARG
;;
n)
NODES=$OPTARG
if [[ $NODES -gt 1 ]]; then
N_STRING=nodes
fi
;;
t)
TYPE=$OPTARG
;;
k)
KEY=$OPTARG
;;
c)
CONTEXT=$OPTARG
;;
y)
FORCE_DELETION=1
;;
*)
usage 1
;;
esac
done
if [[ $(hcloud context active) != "$CONTEXT" ]]; then
echo -e "${Cyan}Hcloud context is not '$CONTEXT'!${NC}"
if hcloud context list | grep -q -F "$CONTEXT"; then
echo -e "${Green}Selecting hcloud context ${CONTEXT}${NC}"
hcloud context use "$CONTEXT"
else
echo -e "${Red}Hcloud context ${CONTEXT} does not exist.${NC}
${Cyan}Will now try to create the context ${CONTEXT}${NC}"
hcloud context create "$CONTEXT"
fi
exit 1
fi
if [[ $DELETE -eq 1 ]]; then
SERVERS=$(hcloud server list -o json)
if [[ $SERVERS == 'null' ]]; then
echo -e "${Cyan}No VPS to delete.${NC}"
exit 0
fi
NAMES=$(echo "$SERVERS" | jq -r '.[] | .name' | sort -h)
echo -e "${Red}You are about to delete the following VPS${NC}:"
echo "$NAMES"
if [[ $FORCE_DELETION -eq 1 ]]; then
confirm="yes"
else
echo -e -n "${Cyan}Please confirm the deletion by typing '${NC}${Red}yes${NC}': "
read -r confirm
fi
if [[ $confirm == 'yes' ]]; then
for i in $NAMES; do
echo -e "${Cyan}Starting server $i deletion${NC}"
delete_server "$i" &
done
# Wait for all delete_server jobs to finish
while true; do
fg > /dev/null 2>&1
[ $? == 1 ] && break
done
if [[ $(hcloud server list -o json) == '[]' ]]; then
echo -e "${Green}All servers have been deleted${NC}"
else
echo -e "${Red}Some servers have not been deleted:${NC}"
hcloud server list
fi
else
echo "Deletion cancelled."
fi
exit 0
fi
if [[ -z $KEY ]]; then
echo -e "${Red}You must choose a ssh key!${NC}\n"
usage 1
fi
KEY_FOUND=0
for i in $(hcloud ssh-key list -o json | jq -r '.[] | .name'); do
if [[ $i == "$KEY" ]]; then
KEY_FOUND=1
break
fi
done
if [[ $KEY_FOUND -eq 0 ]]; then
echo -e "${Red}The chosen ssh key is not registered on Hetzner!${NC}\n"
usage 1
fi
if hcloud server list | grep -q -v NAME; then
echo -e "${Red}There already are servers in the context! Exiting.${NC}\nList of the servers:"
hcloud server list
exit 1
fi
if [[ ! -f "$ENV_FILE" ]]; then
echo -e "${Red}Environment file '$ENV_FILE' does not exist!${NC}"
exit 1
fi
echo -e "${Green}Creating $SERVERS VPS${NC}"
REGIONS=($(hcloud location list -o json | jq -r '.[] | select(.name != "fsn1") | .name' | shuf))
for i in $(seq 1 "$SERVERS"); do
REGION=${REGIONS[$((i % ${#REGIONS[@]}))]}
echo -e "${Cyan}Creating VPS n°$i in $REGION"
create_nodes_server "$i" "$TYPE" "$KEY" "$REGION" &
done
# Wait for all create_nodes_server jobs to finish
while true; do
fg > /dev/null 2>&1
[ $? == 1 ] && break
done
echo -e "${Green}Starting nodes on $SERVERS VPS ($NODES $N_STRING each)${NC}"
for i in $(seq 1 "$SERVERS"); do
echo -e "${Cyan}Starting $N_STRING on VPS n°$i${NC}"
start_nodes "$i" &
done
echo -e "${Green}Waiting for all nodes to be started${NC}"
# Wait for all start_nodes jobs to finish
while true; do
fg > /dev/null 2>&1
[ $? == 1 ] && break
done
echo -e "${Green}All the servers and nodes have been created and started!
Number of servers: $SERVERS
Number of nodes per server: $NODES
Type of the servers:
nodes servers: $TYPE
You can remove all servers with the following command
$0 -d${NC}"

View File

@@ -1,126 +0,0 @@
#!/bin/bash
usage() {
cat << EOF
$(basename "$0") (c) Framasoft 2023, WTPF
USAGE
$(basename "$0") [-h] [-n <int>]
OPTIONS
-h print this help and exit
-n <int> how many selenium nodes you want to launch. Default: 1
-e <string> the environment file path to use. Default: .env
EOF
exit "$1"
}
NUMBER=1
ENV_FILE=".env"
while getopts "hn:i:" option; do
case $option in
h)
usage 0
;;
n)
NUMBER=$OPTARG
;;
e)
ENV_FILE=$OPTARG
;;
*)
usage 1
;;
esac
done
HOST=$(hostname)
DEBIAN_FRONTEND=noninteractive
export DEBIAN_FRONTEND
echo "Installing packages"
apt-get -qq -y update
apt-get -qq -y dist-upgrade
apt-get -qq -y install jq \
tmux \
vim \
multitail \
htop \
liquidprompt \
coreutils \
apparmor-utils \
docker.io \
echo "Activating liquidprompt"
liquidprompt_activate
. /usr/share/liquidprompt/liquidprompt
echo "Modifying kernel parameters"
sysctl net.ipv6.conf.default.forwarding=1
sysctl net.ipv6.conf.all.forwarding=1
echo "Configuring Docker for IPv6"
IP_ADDR=$(ip --json a show eth0 | jq '.[] | .addr_info | .[] | select(.family | contains("inet6")) | select(.scope | contains("global")) | .local' -r)
NETWORK=$(echo "$IP_ADDR" | sed -e 's@:[^:]\+$@8000::/65@')
cat << EOF > /etc/docker/daemon.json
{
"ipv6": true,
"fixed-cidr-v6": "$NETWORK"
}
EOF
systemctl restart docker
echo "Starting $NUMBER Selenium nodes"
for NB in $(seq 1 "$NUMBER"); do
NODE_NAME="selenium-${HOST}-instance-${NB}"
# Replace variables in the environment file
TEMP_ENV_FILE=$(mktemp)
while IFS= read -r line; do
eval "echo \"$line\""
done < "$ENV_FILE" > "$TEMP_ENV_FILE"
ENV_FILE="$TEMP_ENV_FILE"
echo "Starting Selenium node n°$NB"
docker run --rm \
--env-file $ENV_FILE \
--name "$NODE_NAME" \
--pull always \
--shm-size="2g" \
-d \
kobimex/peertube-collector-monolith:latest > /dev/null 2>&1
# Wait until the container gets an IPv6 address.
DOCKER_IP=""
for i in {1..10}; do
DOCKER_IP=$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.GlobalIPv6Address}}{{end}}' "$NODE_NAME")
if [ -n "$DOCKER_IP" ]; then
break
fi
sleep 1
done
if [ -z "$DOCKER_IP" ]; then
echo "Error: Could not retrieve a valid IPv6 address for $NODE_NAME." >&2
exit 1
fi
echo "Adding Selenium node n°$NB to neighbour proxy"
ip -6 neighbour add proxy "$DOCKER_IP" dev eth0
docker stop "$NODE_NAME"
sleep 1
docker run --rm \
--env-file $ENV_FILE \
--name "$NODE_NAME" \
--pull always \
--shm-size="2g" \
-d \
-p 790$NB:790$NB \
-e "SE_NO_VNC_PORT=790$NB" \
kobimex/peertube-collector-monolith:latest > /dev/null 2>&1
done

Binary file not shown.

Binary file not shown.

View File

@@ -7,7 +7,7 @@
dedup_interval = "600s" dedup_interval = "600s"
[[inputs.socket_listener]] [[inputs.socket_listener]]
service_address = "udp://:${SOCKET_PORT}" service_address = "udp://:8094"
data_format = "xpath_json" data_format = "xpath_json"
[[inputs.socket_listener.xpath]] [[inputs.socket_listener.xpath]]
metric_name = "'peertube'" metric_name = "'peertube'"

View File

@@ -1,29 +0,0 @@
import abc
import json
import socket
import logging
from selenium import webdriver
logger = logging.getLogger(__name__)
# Abstract Base Classes for Plugins
class StatsSetupPlugin(abc.ABC):
@abc.abstractmethod
def setup_stats(self, driver: webdriver.Remote | webdriver.Chrome, url: str, retries: int = 5) -> webdriver.Remote | webdriver.Chrome:
pass
class StatsDownloadPlugin(abc.ABC):
@abc.abstractmethod
def download_stats(self, driver: webdriver.Remote | webdriver.Chrome, peersDict: dict, socket_url: str, socket_port: int):
pass
@staticmethod
def saveStats(stats: list, socket_url: str, socket_port: int):
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
logger.debug(f'Saving stats: {json.dumps(stats, indent=4)}')
sock.sendto(json.dumps(stats).encode(), (socket_url, socket_port))
sock.close()
logger.debug('Sent stats to socket.')
except socket.error as e:
logger.error(f'Got socket error: {e}')

View File

@@ -12,7 +12,6 @@
"babel-loader": "^8.2.2", "babel-loader": "^8.2.2",
"clean-webpack-plugin": "^3.0.0", "clean-webpack-plugin": "^3.0.0",
"copy-webpack-plugin": "^12.0.2", "copy-webpack-plugin": "^12.0.2",
"dotenv": "^16.4.7",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"html-webpack-plugin": "^5.3.1", "html-webpack-plugin": "^5.3.1",
"mini-css-extract-plugin": "^1.6.0", "mini-css-extract-plugin": "^1.6.0",
@@ -3900,19 +3899,6 @@
"tslib": "^2.0.3" "tslib": "^2.0.3"
} }
}, },
"node_modules/dotenv": {
"version": "16.4.7",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
"integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/dunder-proto": { "node_modules/dunder-proto": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",

View File

@@ -12,7 +12,6 @@
"babel-loader": "^8.2.2", "babel-loader": "^8.2.2",
"clean-webpack-plugin": "^3.0.0", "clean-webpack-plugin": "^3.0.0",
"copy-webpack-plugin": "^12.0.2", "copy-webpack-plugin": "^12.0.2",
"dotenv": "^16.4.7",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"html-webpack-plugin": "^5.3.1", "html-webpack-plugin": "^5.3.1",
"mini-css-extract-plugin": "^1.6.0", "mini-css-extract-plugin": "^1.6.0",

View File

@@ -1,7 +1,5 @@
const path = require('path'); const path = require('path');
const { EnvironmentPlugin } = require('webpack'); const { EnvironmentPlugin } = require('webpack');
const envPath = path.resolve(__dirname, '../../.env');
const envConfig = require('dotenv').config({ path: envPath }).parsed;
module.exports = (env) => { module.exports = (env) => {
const url = env.URL || 'http://localhost'; const url = env.URL || 'http://localhost';
@@ -27,7 +25,7 @@ module.exports = (env) => {
}, },
plugins: [ plugins: [
new EnvironmentPlugin({ new EnvironmentPlugin({
WEBRTC_INTERNALS_EXPORTER_URL: envConfig.WEBRTC_INTERNALS_EXPORTER_URL || url WEBRTC_INTERNALS_EXPORTER_URL: url,
}), }),
], ],
}; };