diff --git a/tuxinet-streamhub-v2.1-enterprise/.env.example b/tuxinet-streamhub-v2.1-enterprise/.env.example deleted file mode 100644 index 454fd40..0000000 --- a/tuxinet-streamhub-v2.1-enterprise/.env.example +++ /dev/null @@ -1,26 +0,0 @@ - -SECRET_KEY=change-me-super-secret -TZ=Europe/Copenhagen -PUBLIC_BASE_URL=https://rtmp.djgulvbass.dk -MEDIA_BASE_URL=http://mediamtx:8888 -MEDIA_PUBLIC_HLS_BASE=https://rtmp.djgulvbass.dk/hls -MEDIAMTX_API_URL=http://mediamtx:9997 - -POSTGRES_DB=streamhub -POSTGRES_USER=streamhub -POSTGRES_PASSWORD=change-me-db -DATABASE_URL=postgresql+psycopg://streamhub:change-me-db@postgres:5432/streamhub - -ADMIN_USERNAME=admin -ADMIN_EMAIL=admin@tuxinet.dk -ADMIN_PASSWORD=change-me-admin - -SMTP_HOST=mail.smtp2go.com -SMTP_PORT=2525 -SMTP_USERNAME=noreplay@tuxinet.dk -SMTP_PASSWORD=change-me-smtp -MAIL_FROM=noreplay@tuxinet.dk -MAIL_ENABLED=true - -RESET_TOKEN_TTL_MINUTES=60 -FORCE_HTTPS_URLS=true diff --git a/tuxinet-streamhub-v2.1-enterprise/README.md b/tuxinet-streamhub-v2.1-enterprise/README.md deleted file mode 100644 index 684491a..0000000 --- a/tuxinet-streamhub-v2.1-enterprise/README.md +++ /dev/null @@ -1,83 +0,0 @@ - -# TuxiNet StreamHub v2.1 Enterprise (Clean Build) - -Første enterprise-udgave af StreamHub med: - -- PostgreSQL -- Flask backend -- MediaMTX auth via backend -- Admin + streamer roller -- 1 streamer = 1 stream -- SMTP2GO mailflow -- Force password change ved første login -- Password reset via mail -- Branding med `bg.png`, `logo.png`, `videooffline.png` - -## Status - -Denne første udgave er lavet som en **ren base**, så du kan starte rent uden de gamle v2-filer. - -### Implementeret -- Datamodel låst til PostgreSQL -- Admin-login og streamer-login -- Admin kan oprette streamer + stream i samme flow -- Streamer ser kun egen stream -- MediaMTX auth-route -- Status fra MediaMTX API -- Mail helper til onboarding og reset -- Force password change flow -- Forgot/reset password flow -- Offline player image i UI -- Enterprise login design - -### Delvist scaffoldet / klar til næste iteration -- Action/bot layer (`kick publisher`, `restart service`) er scaffoldet som placeholder -- Mere avancerede live-metrics kan udvides senere -- Fuldt migrationsflow med Alembic kan tilføjes senere - -## Hurtig opstart - -```bash -cp .env.example .env -nano .env - -docker compose build --no-cache -docker compose up -d -``` - -Åbn derefter: -- Web: `http://IP` eller dit reverse proxied domæne -- MediaMTX API: internt på `http://mediamtx:9997` - -## Første admin-bruger - -Backend opretter automatisk admin-brugeren ud fra `.env`: - -- `ADMIN_USERNAME` -- `ADMIN_EMAIL` -- `ADMIN_PASSWORD` - -## SMTP2GO - -Sæt i `.env`: - -```env -SMTP_HOST=mail.smtp2go.com -SMTP_PORT=2525 -SMTP_USERNAME=noreplay@tuxinet.dk -SMTP_PASSWORD=DIN_SMTP2GO_ADGANGSKODE -MAIL_FROM=noreplay@tuxinet.dk -``` - -## MediaMTX ingest-formater - -### OBS -- Server: `rtmp://dit-domæne:1935/` -- Key: `` - -### Larix / Streamlabs fallback -- URL: `rtmp://dit-domæne:1935` -- Key: `/` - -### Query-token -- `rtmp://dit-domæne:1935/?token=` diff --git a/tuxinet-streamhub-v2.1-enterprise/backend/Dockerfile b/tuxinet-streamhub-v2.1-enterprise/backend/Dockerfile deleted file mode 100644 index 3b516a3..0000000 --- a/tuxinet-streamhub-v2.1-enterprise/backend/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ - -FROM python:3.12-slim - -WORKDIR /app -ENV PYTHONDONTWRITEBYTECODE=1 -ENV PYTHONUNBUFFERED=1 - -RUN apt-get update && apt-get install -y --no-install-recommends ffmpeg curl && rm -rf /var/lib/apt/lists/* - -COPY requirements.txt . -RUN pip install --no-cache-dir -r requirements.txt - -COPY . . - -CMD ["gunicorn", "-w", "2", "-b", "0.0.0.0:5000", "wsgi:app"] diff --git a/tuxinet-streamhub-v2.1-enterprise/backend/__pycache__/wsgi.cpython-313.pyc b/tuxinet-streamhub-v2.1-enterprise/backend/__pycache__/wsgi.cpython-313.pyc deleted file mode 100644 index ee5830d..0000000 Binary files a/tuxinet-streamhub-v2.1-enterprise/backend/__pycache__/wsgi.cpython-313.pyc and /dev/null differ diff --git a/tuxinet-streamhub-v2.1-enterprise/backend/app/__init__.py b/tuxinet-streamhub-v2.1-enterprise/backend/app/__init__.py deleted file mode 100644 index cb85886..0000000 --- a/tuxinet-streamhub-v2.1-enterprise/backend/app/__init__.py +++ /dev/null @@ -1,34 +0,0 @@ - -import os -from flask import Flask - -from .models import db -from .routes import bp -from .services.bootstrap import bootstrap_system - - -def create_app(): - app = Flask(__name__, instance_relative_config=True) - app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', 'change-me') - app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv('DATABASE_URL') - app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False - app.config['PUBLIC_BASE_URL'] = os.getenv('PUBLIC_BASE_URL', 'http://localhost') - app.config['MEDIA_BASE_URL'] = os.getenv('MEDIA_BASE_URL', 'http://mediamtx:8888') - app.config['MEDIA_PUBLIC_HLS_BASE'] = os.getenv('MEDIA_PUBLIC_HLS_BASE', 'http://localhost/hls') - app.config['MEDIAMTX_API_URL'] = os.getenv('MEDIAMTX_API_URL', 'http://mediamtx:9997') - app.config['SMTP_HOST'] = os.getenv('SMTP_HOST', '') - app.config['SMTP_PORT'] = int(os.getenv('SMTP_PORT', '2525')) - app.config['SMTP_USERNAME'] = os.getenv('SMTP_USERNAME', '') - app.config['SMTP_PASSWORD'] = os.getenv('SMTP_PASSWORD', '') - app.config['MAIL_FROM'] = os.getenv('MAIL_FROM', os.getenv('SMTP_USERNAME', 'noreply@example.com')) - app.config['MAIL_ENABLED'] = os.getenv('MAIL_ENABLED', 'true').lower() == 'true' - app.config['RESET_TOKEN_TTL_MINUTES'] = int(os.getenv('RESET_TOKEN_TTL_MINUTES', '60')) - - db.init_app(app) - app.register_blueprint(bp) - - with app.app_context(): - db.create_all() - bootstrap_system(app) - - return app diff --git a/tuxinet-streamhub-v2.1-enterprise/backend/app/__pycache__/__init__.cpython-313.pyc b/tuxinet-streamhub-v2.1-enterprise/backend/app/__pycache__/__init__.cpython-313.pyc deleted file mode 100644 index 7a5da89..0000000 Binary files a/tuxinet-streamhub-v2.1-enterprise/backend/app/__pycache__/__init__.cpython-313.pyc and /dev/null differ diff --git a/tuxinet-streamhub-v2.1-enterprise/backend/app/__pycache__/models.cpython-313.pyc b/tuxinet-streamhub-v2.1-enterprise/backend/app/__pycache__/models.cpython-313.pyc deleted file mode 100644 index 903d5ae..0000000 Binary files a/tuxinet-streamhub-v2.1-enterprise/backend/app/__pycache__/models.cpython-313.pyc and /dev/null differ diff --git a/tuxinet-streamhub-v2.1-enterprise/backend/app/__pycache__/routes.cpython-313.pyc b/tuxinet-streamhub-v2.1-enterprise/backend/app/__pycache__/routes.cpython-313.pyc deleted file mode 100644 index 2d34f6a..0000000 Binary files a/tuxinet-streamhub-v2.1-enterprise/backend/app/__pycache__/routes.cpython-313.pyc and /dev/null differ diff --git a/tuxinet-streamhub-v2.1-enterprise/backend/app/__pycache__/utils.cpython-313.pyc b/tuxinet-streamhub-v2.1-enterprise/backend/app/__pycache__/utils.cpython-313.pyc deleted file mode 100644 index 1c4bdb0..0000000 Binary files a/tuxinet-streamhub-v2.1-enterprise/backend/app/__pycache__/utils.cpython-313.pyc and /dev/null differ diff --git a/tuxinet-streamhub-v2.1-enterprise/backend/app/models.py b/tuxinet-streamhub-v2.1-enterprise/backend/app/models.py deleted file mode 100644 index 1306fa6..0000000 --- a/tuxinet-streamhub-v2.1-enterprise/backend/app/models.py +++ /dev/null @@ -1,85 +0,0 @@ - -from datetime import datetime, timezone -from werkzeug.security import check_password_hash, generate_password_hash -from flask_sqlalchemy import SQLAlchemy - - -db = SQLAlchemy() - - -def utcnow(): - return datetime.now(timezone.utc) - - -class User(db.Model): - id = db.Column(db.Integer, primary_key=True) - username = db.Column(db.String(120), unique=True, nullable=False) - email = db.Column(db.String(255), unique=True, nullable=False) - display_name = db.Column(db.String(255), nullable=False) - password_hash = db.Column(db.String(255), nullable=False) - role = db.Column(db.String(20), nullable=False, default='streamer') - active = db.Column(db.Boolean, nullable=False, default=True) - force_password_change = db.Column(db.Boolean, nullable=False, default=True) - reset_token = db.Column(db.String(255), nullable=True) - reset_token_expires_at = db.Column(db.DateTime(timezone=True), nullable=True) - created_at = db.Column(db.DateTime(timezone=True), nullable=False, default=utcnow) - updated_at = db.Column(db.DateTime(timezone=True), nullable=False, default=utcnow, onupdate=utcnow) - - stream = db.relationship('Stream', back_populates='user', uselist=False) - - def set_password(self, raw: str): - self.password_hash = generate_password_hash(raw) - - def check_password(self, raw: str) -> bool: - return check_password_hash(self.password_hash, raw) - - -class Stream(db.Model): - id = db.Column(db.Integer, primary_key=True) - user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False, unique=True) - name = db.Column(db.String(255), nullable=False) - slug = db.Column(db.String(120), unique=True, nullable=False) - token = db.Column(db.String(255), nullable=False) - chat_type = db.Column(db.String(30), nullable=False, default='none') - chat_url = db.Column(db.Text, nullable=True) - notes = db.Column(db.Text, nullable=True) - active = db.Column(db.Boolean, nullable=False, default=True) - is_live = db.Column(db.Boolean, nullable=False, default=False) - created_at = db.Column(db.DateTime(timezone=True), nullable=False, default=utcnow) - updated_at = db.Column(db.DateTime(timezone=True), nullable=False, default=utcnow, onupdate=utcnow) - - user = db.relationship('User', back_populates='stream') - sessions = db.relationship('StreamSession', back_populates='stream', cascade='all, delete-orphan') - logs = db.relationship('EventLog', back_populates='stream', cascade='all, delete-orphan') - - -class StreamSession(db.Model): - id = db.Column(db.Integer, primary_key=True) - stream_id = db.Column(db.Integer, db.ForeignKey('stream.id'), nullable=False) - started_at = db.Column(db.DateTime(timezone=True), nullable=False, default=utcnow) - ended_at = db.Column(db.DateTime(timezone=True), nullable=True) - status = db.Column(db.String(50), nullable=False, default='offline') - source_type = db.Column(db.String(50), nullable=True) - source_id = db.Column(db.String(120), nullable=True) - publisher_ip = db.Column(db.String(64), nullable=True) - video_codec = db.Column(db.String(64), nullable=True) - audio_codec = db.Column(db.String(64), nullable=True) - audio_channels = db.Column(db.String(32), nullable=True) - audio_sample_rate = db.Column(db.String(32), nullable=True) - width = db.Column(db.Integer, nullable=True) - height = db.Column(db.Integer, nullable=True) - fps = db.Column(db.String(32), nullable=True) - bitrate = db.Column(db.String(64), nullable=True) - - stream = db.relationship('Stream', back_populates='sessions') - - -class EventLog(db.Model): - id = db.Column(db.Integer, primary_key=True) - stream_id = db.Column(db.Integer, db.ForeignKey('stream.id'), nullable=False) - user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True) - level = db.Column(db.String(20), nullable=False, default='info') - message = db.Column(db.Text, nullable=False) - created_at = db.Column(db.DateTime(timezone=True), nullable=False, default=utcnow) - - stream = db.relationship('Stream', back_populates='logs') diff --git a/tuxinet-streamhub-v2.1-enterprise/backend/app/routes.py b/tuxinet-streamhub-v2.1-enterprise/backend/app/routes.py deleted file mode 100644 index 14136ce..0000000 --- a/tuxinet-streamhub-v2.1-enterprise/backend/app/routes.py +++ /dev/null @@ -1,412 +0,0 @@ - -from datetime import datetime, timedelta, timezone -from functools import wraps -import secrets - -import requests -from flask import ( - Blueprint, current_app, flash, jsonify, redirect, render_template, - request, session, url_for -) - -from .models import EventLog, Stream, StreamSession, User, db -from .services.mailer import send_reset_email, send_welcome_email -from .utils import ffprobe_stream, generate_password, generate_token, hls_url, ingest_url_with_query - -bp = Blueprint('main', __name__) - - -def current_user(): - uid = session.get('user_id') - if not uid: - return None - return db.session.get(User, uid) - - -def log_event(stream_id, message, level='info', user_id=None): - db.session.add(EventLog(stream_id=stream_id, message=message, level=level, user_id=user_id)) - db.session.commit() - - -def login_required(role=None): - def deco(fn): - @wraps(fn) - def wrapper(*args, **kwargs): - user = current_user() - if not user or not user.active: - return redirect(url_for('main.login')) - if role and user.role != role: - return redirect(url_for('main.index')) - return fn(*args, **kwargs) - return wrapper - return deco - - -def stream_for_current_user_or_403(stream_id): - user = current_user() - stream = db.session.get(Stream, stream_id) - if not stream: - return None - if user.role == 'admin': - return stream - if stream.user_id != user.id: - return None - return stream - - -def media_api_paths(): - try: - res = requests.get(f"{current_app.config['MEDIAMTX_API_URL'].rstrip('/')}/v3/paths/list", timeout=2) - res.raise_for_status() - return res.json().get('items', []) - except Exception as exc: - current_app.logger.warning(f'MediaMTX API unavailable: {exc}') - return [] - - -def path_info_for(slug): - for item in media_api_paths(): - if item.get('name') == slug: - return item - return None - - -def extract_slug_token_from_auth(data: dict): - path = (data.get('path') or '').strip().strip('/') - query = (data.get('query') or '').strip() - user = (data.get('user') or '').strip() - password = (data.get('password') or '').strip() - - slug = path - token = None - if '/' in path: - slug, token = path.split('/', 1) - slug = slug.strip() - token = token.strip() - - if not token and query: - from urllib.parse import parse_qs - token = parse_qs(query).get('token', [None])[0] - - if not token and password: - token = password - if not token and user and user.lower() != 'any': - token = user - return slug, token - - -def update_live_state(stream: Stream): - info = path_info_for(stream.slug) - stream.is_live = bool(info and (info.get('ready') or info.get('online'))) - db.session.commit() - return info - - -def stats_for(stream: Stream): - if not stream.is_live: - return {} - return ffprobe_stream(hls_url(current_app.config['MEDIA_BASE_URL'], stream.slug)) - - -@bp.app_context_processor -def inject_globals(): - return {'current_user': current_user()} - - -@bp.get('/') -def index(): - user = current_user() - if not user: - return redirect(url_for('main.login')) - if user.role == 'admin': - return redirect(url_for('main.admin_dashboard')) - if user.stream: - return redirect(url_for('main.streamer_panel', stream_id=user.stream.id)) - flash('Din konto har ingen stream knyttet endnu.', 'error') - return redirect(url_for('main.logout')) - - -@bp.route('/login', methods=['GET', 'POST']) -def login(): - if request.method == 'POST': - username = request.form.get('username', '').strip() - password = request.form.get('password', '') - user = User.query.filter_by(username=username).first() - if user and user.active and user.check_password(password): - session.clear() - session['user_id'] = user.id - if user.force_password_change: - return redirect(url_for('main.force_password_change')) - if user.role == 'admin': - return redirect(url_for('main.admin_dashboard')) - return redirect(url_for('main.index')) - flash('Forkert brugernavn eller kodeord.', 'error') - return render_template('login.html') - - -@bp.get('/logout') -def logout(): - session.clear() - return redirect(url_for('main.login')) - - -@bp.route('/force-password-change', methods=['GET', 'POST']) -@login_required() -def force_password_change(): - user = current_user() - if request.method == 'POST': - p1 = request.form.get('password', '') - p2 = request.form.get('password_confirm', '') - if len(p1) < 10: - flash('Kode skal være mindst 10 tegn.', 'error') - elif p1 != p2: - flash('Koderne matcher ikke.', 'error') - else: - user.set_password(p1) - user.force_password_change = False - db.session.commit() - flash('Din kode er ændret.', 'success') - return redirect(url_for('main.index')) - return render_template('force_password_change.html') - - -@bp.route('/forgot-password', methods=['GET', 'POST']) -def forgot_password(): - if request.method == 'POST': - identity = request.form.get('identity', '').strip() - user = User.query.filter((User.username == identity) | (User.email == identity)).first() - if user: - token = secrets.token_urlsafe(32) - user.reset_token = token - user.reset_token_expires_at = datetime.now(timezone.utc) + timedelta(minutes=current_app.config['RESET_TOKEN_TTL_MINUTES']) - db.session.commit() - reset_url = url_for('main.reset_password', token=token, _external=True) - send_reset_email(user, reset_url) - flash('Hvis brugeren findes, er der sendt et reset-link.', 'success') - return render_template('forgot_password.html') - - -@bp.route('/reset-password/', methods=['GET', 'POST']) -def reset_password(token): - user = User.query.filter_by(reset_token=token).first() - if not user or not user.reset_token_expires_at or user.reset_token_expires_at < datetime.now(timezone.utc): - flash('Reset-link er ugyldigt eller udløbet.', 'error') - return redirect(url_for('main.login')) - - if request.method == 'POST': - p1 = request.form.get('password', '') - p2 = request.form.get('password_confirm', '') - if len(p1) < 10: - flash('Kode skal være mindst 10 tegn.', 'error') - elif p1 != p2: - flash('Koderne matcher ikke.', 'error') - else: - user.set_password(p1) - user.force_password_change = False - user.reset_token = None - user.reset_token_expires_at = None - db.session.commit() - flash('Din kode er nulstillet. Log ind nu.', 'success') - return redirect(url_for('main.login')) - return render_template('reset_password.html') - - -@bp.get('/admin/dashboard') -@login_required('admin') -def admin_dashboard(): - users = User.query.order_by(User.created_at.desc()).all() - streams = Stream.query.order_by(Stream.created_at.desc()).all() - path_map = {item.get('name'): item for item in media_api_paths()} - return render_template('admin_dashboard.html', users=users, streams=streams, path_map=path_map) - - -@bp.route('/admin/streamers/new', methods=['GET', 'POST']) -@login_required('admin') -def create_streamer(): - if request.method == 'POST': - display_name = request.form.get('display_name', '').strip() - username = request.form.get('username', '').strip().lower() - email = request.form.get('email', '').strip().lower() - stream_name = request.form.get('stream_name', '').strip() - slug = request.form.get('slug', '').strip().lower().replace(' ', '-').replace('/', '-') - chat_type = request.form.get('chat_type', 'none') - chat_url = request.form.get('chat_url', '').strip() - notes = request.form.get('notes', '').strip() - - if not all([display_name, username, email, stream_name, slug]): - flash('Alle obligatoriske felter skal udfyldes.', 'error') - return render_template('create_streamer.html') - if User.query.filter((User.username == username) | (User.email == email)).first(): - flash('Brugernavn eller e-mail findes allerede.', 'error') - return render_template('create_streamer.html') - if Stream.query.filter_by(slug=slug).first(): - flash('Slug findes allerede.', 'error') - return render_template('create_streamer.html') - - temp_password = generate_password() - user = User( - username=username, - email=email, - display_name=display_name, - role='streamer', - active=True, - force_password_change=True, - ) - user.set_password(temp_password) - db.session.add(user) - db.session.flush() - - stream = Stream( - user_id=user.id, - name=stream_name, - slug=slug, - token=generate_token(), - chat_type=chat_type, - chat_url=chat_url, - notes=notes, - active=True, - ) - db.session.add(stream) - db.session.commit() - send_welcome_email(user, temp_password) - log_event(stream.id, 'Streamer og stream oprettet', user_id=current_user().id) - flash('Streamer og stream oprettet. Mail er sendt.', 'success') - return redirect(url_for('main.stream_detail_admin', stream_id=stream.id)) - return render_template('create_streamer.html') - - -@bp.get('/admin/streams/') -@login_required('admin') -def stream_detail_admin(stream_id): - stream = db.session.get(Stream, stream_id) - if not stream: - return redirect(url_for('main.admin_dashboard')) - path_info = update_live_state(stream) - stats = stats_for(stream) - sessions = StreamSession.query.filter_by(stream_id=stream.id).order_by(StreamSession.started_at.desc()).limit(20).all() - logs = EventLog.query.filter_by(stream_id=stream.id).order_by(EventLog.created_at.desc()).limit(50).all() - host = current_app.config['PUBLIC_BASE_URL'].replace('https://', '').replace('http://', '').rstrip('/') - return render_template( - 'stream_detail_admin.html', - stream=stream, - stats=stats, - sessions=sessions, - logs=logs, - path_info=path_info, - public_hls=hls_url(current_app.config['MEDIA_PUBLIC_HLS_BASE'], stream.slug), - ingest_full=ingest_url_with_query(host, stream.slug, stream.token), - ingest_server=f'rtmp://{host}:1935/{stream.slug}', - ingest_key=stream.token, - ingest_alt_server=f'rtmp://{host}:1935', - ingest_alt_key=f'{stream.slug}/{stream.token}', - ) - - -@bp.post('/admin/streams//rotate-token') -@login_required('admin') -def rotate_stream_token(stream_id): - stream = db.session.get(Stream, stream_id) - if not stream: - return redirect(url_for('main.admin_dashboard')) - stream.token = generate_token() - db.session.commit() - log_event(stream.id, 'Token roteret', 'warning', current_user().id) - flash('Token er roteret.', 'success') - return redirect(url_for('main.stream_detail_admin', stream_id=stream.id)) - - -@bp.post('/admin/streams//toggle') -@login_required('admin') -def toggle_stream(stream_id): - stream = db.session.get(Stream, stream_id) - if not stream: - return redirect(url_for('main.admin_dashboard')) - stream.active = not stream.active - db.session.commit() - log_event(stream.id, f"Stream {'aktiveret' if stream.active else 'deaktiveret'}", 'warning', current_user().id) - flash('Stream-status er ændret.', 'success') - return redirect(url_for('main.stream_detail_admin', stream_id=stream.id)) - - -@bp.post('/admin/users//send-reset') -@login_required('admin') -def admin_send_reset(user_id): - user = db.session.get(User, user_id) - if not user: - return redirect(url_for('main.admin_dashboard')) - token = secrets.token_urlsafe(32) - user.reset_token = token - user.reset_token_expires_at = datetime.now(timezone.utc) + timedelta(minutes=current_app.config['RESET_TOKEN_TTL_MINUTES']) - db.session.commit() - reset_url = url_for('main.reset_password', token=token, _external=True) - send_reset_email(user, reset_url) - flash('Reset-mail sendt.', 'success') - return redirect(url_for('main.admin_dashboard')) - - -@bp.get('/streamer/panel/') -@login_required() -def streamer_panel(stream_id): - stream = stream_for_current_user_or_403(stream_id) - if not stream: - return redirect(url_for('main.index')) - path_info = update_live_state(stream) - stats = stats_for(stream) - sessions = StreamSession.query.filter_by(stream_id=stream.id).order_by(StreamSession.started_at.desc()).limit(10).all() - return render_template( - 'streamer_panel.html', - stream=stream, - stats=stats, - sessions=sessions, - path_info=path_info, - public_hls=hls_url(current_app.config['MEDIA_PUBLIC_HLS_BASE'], stream.slug), - ) - - -@bp.get('/api/streams//status') -@login_required() -def stream_status(stream_id): - stream = stream_for_current_user_or_403(stream_id) - if not stream: - return jsonify({'error': 'not found'}), 404 - path_info = update_live_state(stream) - stats = stats_for(stream) - return jsonify({ - 'id': stream.id, - 'slug': stream.slug, - 'is_live': stream.is_live, - 'path_info': path_info, - 'stats': stats, - 'preview_url': hls_url(current_app.config['MEDIA_PUBLIC_HLS_BASE'], stream.slug), - 'chat_url': stream.chat_url, - }) - - -@bp.route('/api/mediamtx/auth', methods=['POST']) -def mediamtx_auth(): - data = request.get_json(silent=True) or request.form.to_dict() or {} - action = (data.get('action') or '').strip() - slug, token = extract_slug_token_from_auth(data) - - if action == 'read': - return 'ok', 200 - if action != 'publish': - return 'denied', 401 - if not slug or not token: - return 'denied', 401 - - stream = Stream.query.filter_by(slug=slug, active=True).first() - if not stream or stream.token != token: - return 'denied', 401 - - ip = data.get('ip') - session_row = StreamSession(stream_id=stream.id, status='live', publisher_ip=ip, source_type=data.get('protocol'), source_id=data.get('id')) - db.session.add(session_row) - stream.is_live = True - db.session.commit() - return 'ok', 200 - - -@bp.get('/healthz') -def healthz(): - return jsonify({'ok': True}) diff --git a/tuxinet-streamhub-v2.1-enterprise/backend/app/services/__pycache__/bootstrap.cpython-313.pyc b/tuxinet-streamhub-v2.1-enterprise/backend/app/services/__pycache__/bootstrap.cpython-313.pyc deleted file mode 100644 index e81db2e..0000000 Binary files a/tuxinet-streamhub-v2.1-enterprise/backend/app/services/__pycache__/bootstrap.cpython-313.pyc and /dev/null differ diff --git a/tuxinet-streamhub-v2.1-enterprise/backend/app/services/__pycache__/mailer.cpython-313.pyc b/tuxinet-streamhub-v2.1-enterprise/backend/app/services/__pycache__/mailer.cpython-313.pyc deleted file mode 100644 index 7fc4aa9..0000000 Binary files a/tuxinet-streamhub-v2.1-enterprise/backend/app/services/__pycache__/mailer.cpython-313.pyc and /dev/null differ diff --git a/tuxinet-streamhub-v2.1-enterprise/backend/app/services/bootstrap.py b/tuxinet-streamhub-v2.1-enterprise/backend/app/services/bootstrap.py deleted file mode 100644 index 234690e..0000000 --- a/tuxinet-streamhub-v2.1-enterprise/backend/app/services/bootstrap.py +++ /dev/null @@ -1,28 +0,0 @@ - -import os -from flask import current_app - -from ..models import User, db - - -def bootstrap_system(app): - admin_username = os.getenv('ADMIN_USERNAME', 'admin').strip() - admin_email = os.getenv('ADMIN_EMAIL', 'admin@example.com').strip() - admin_password = os.getenv('ADMIN_PASSWORD', 'change-me-admin').strip() - - existing = User.query.filter_by(username=admin_username).first() - if existing: - return - - admin = User( - username=admin_username, - email=admin_email, - display_name='Administrator', - role='admin', - active=True, - force_password_change=False, - ) - admin.set_password(admin_password) - db.session.add(admin) - db.session.commit() - current_app.logger.info(f'Bootstrap admin created: {admin_username}') diff --git a/tuxinet-streamhub-v2.1-enterprise/backend/app/services/mailer.py b/tuxinet-streamhub-v2.1-enterprise/backend/app/services/mailer.py deleted file mode 100644 index 32079bb..0000000 --- a/tuxinet-streamhub-v2.1-enterprise/backend/app/services/mailer.py +++ /dev/null @@ -1,55 +0,0 @@ -import smtplib -from email.message import EmailMessage -from flask import current_app, url_for - - -def _send_mail(to_email: str, subject: str, body: str): - if not current_app.config['MAIL_ENABLED']: - current_app.logger.info(f"MAIL DISABLED -> To: {to_email} | Subject: {subject}\n{body}") - return - - msg = EmailMessage() - msg['Subject'] = subject - msg['From'] = current_app.config['MAIL_FROM'] - msg['To'] = to_email - msg.set_content(body) - - with smtplib.SMTP(current_app.config['SMTP_HOST'], current_app.config['SMTP_PORT'], timeout=20) as smtp: - smtp.starttls() - smtp.login(current_app.config['SMTP_USERNAME'], current_app.config['SMTP_PASSWORD']) - smtp.send_message(msg) - - -def send_welcome_email(user, temporary_password: str): - login_url = url_for('main.login', _external=True) - body = f"""Hej {user.display_name}, - -Din TuxiNet StreamHub-konto er blevet oprettet. - -Brugernavn: {user.username} -Midlertidig kode: {temporary_password} -Login: {login_url} - -Du skal ændre din kode ved første login. - -Hilsen -TuxiNet StreamHub -""" - _send_mail(user.email, 'Velkommen til TuxiNet StreamHub', body) - - -def send_reset_email(user, reset_url: str): - body = f"""Hej {user.display_name}, - -Du har bedt om at nulstille din adgangskode. - -Reset-link: -{reset_url} - -Linket udløber automatisk. -Hvis du ikke har bedt om dette, kan du ignorere mailen. - -Hilsen -TuxiNet StreamHub -""" - _send_mail(user.email, 'Nulstil din adgangskode', body) diff --git a/tuxinet-streamhub-v2.1-enterprise/backend/app/static/bg.png b/tuxinet-streamhub-v2.1-enterprise/backend/app/static/bg.png deleted file mode 100644 index a09a131..0000000 Binary files a/tuxinet-streamhub-v2.1-enterprise/backend/app/static/bg.png and /dev/null differ diff --git a/tuxinet-streamhub-v2.1-enterprise/backend/app/static/logo.png b/tuxinet-streamhub-v2.1-enterprise/backend/app/static/logo.png deleted file mode 100644 index 314b550..0000000 Binary files a/tuxinet-streamhub-v2.1-enterprise/backend/app/static/logo.png and /dev/null differ diff --git a/tuxinet-streamhub-v2.1-enterprise/backend/app/static/style.css b/tuxinet-streamhub-v2.1-enterprise/backend/app/static/style.css deleted file mode 100644 index f7c56af..0000000 --- a/tuxinet-streamhub-v2.1-enterprise/backend/app/static/style.css +++ /dev/null @@ -1,60 +0,0 @@ - -:root { - --bg-dark: #08111b; - --bg-panel: rgba(12, 20, 32, 0.88); - --bg-panel-2: rgba(9, 16, 27, 0.95); - --accent: #00d8ff; - --accent-2: #21b8ff; - --text-main: #eef7ff; - --text-soft: #9fb8cc; - --border: rgba(0, 216, 255, 0.18); - --ok: #78f5b0; - --bad: #ff7f96; -} -* { box-sizing: border-box; } -body { margin: 0; font-family: Inter, Arial, sans-serif; background: radial-gradient(circle at top, #0b1c2f, #08111b 55%); color: var(--text-main); } -body.login-page { background: url('/static/bg.png') center center / cover no-repeat fixed; } -a { color: var(--accent); text-decoration: none; } -.container { max-width: 1360px; margin: 0 auto; padding: 2rem; } -.topbar { display:flex; justify-content:space-between; align-items:center; padding:1rem 1.5rem; background: rgba(4,10,18,.9); border-bottom:1px solid var(--border); position:sticky; top:0; z-index:20; } -.topbar nav { display:flex; gap:1rem; } -.brand { display:flex; align-items:center; gap:.75rem; color:var(--text-main); font-weight:700; } -.brand img { height:38px; } -.card { background: var(--bg-panel); border: 1px solid var(--border); border-radius: 20px; padding: 1.2rem; box-shadow: 0 0 40px rgba(0,0,0,.18); } -.narrow { max-width: 520px; margin: 2rem auto; } -.page-header { display:flex; justify-content:space-between; align-items:center; gap:1rem; margin-bottom:1.25rem; } -.button, button { border:0; border-radius:14px; padding:.85rem 1.15rem; cursor:pointer; font-weight:700; background: linear-gradient(135deg, var(--accent), var(--accent-2)); color:#03111a; } -.button.secondary, button.secondary { background: rgba(255,255,255,.06); color: var(--text-main); border:1px solid var(--border); } -.form-grid { display:grid; gap:1rem; } -label { display:grid; gap:.35rem; color:var(--text-soft); } -input, textarea, select { width:100%; padding:.9rem 1rem; border-radius:14px; border:1px solid var(--border); background: rgba(0,0,0,.25); color:var(--text-main); } -textarea { min-height: 120px; } -.flash { padding: .9rem 1rem; border-radius: 12px; margin-bottom: 1rem; } -.flash.success { background:#173726; color:#a9f3c7; } -.flash.error { background:#3c1920; color:#ffc1cf; } -.login-wrap { min-height: calc(100vh - 96px); display:grid; place-items:center; } -.login-card { width:min(480px, 92vw); padding:2rem; border-radius:26px; background: rgba(8, 17, 27, .74); border:1px solid var(--border); backdrop-filter: blur(14px); } -.login-logo { width: 100%; max-width: 240px; display:block; margin:0 auto 1rem; } -.login-links { margin-top: 1rem; text-align:center; } -.muted { color:var(--text-soft); } -.stream-table { width:100%; border-collapse: collapse; } -.stream-table th, .stream-table td { padding:.85rem .75rem; border-bottom:1px solid rgba(255,255,255,.08); text-align:left; vertical-align:middle; } -.space-top { margin-top: 1rem; } -.detail-layout { display:grid; grid-template-columns:1.45fr .75fr; gap:1rem; } -.lower-gap { margin-top: 1rem; } -.streamer-layout-fixed { display:grid; grid-template-columns:1.45fr .75fr; gap:1rem; } -.panel-head { display:flex; justify-content:space-between; align-items:center; gap:1rem; margin-bottom:1rem; } -.video-frame { background:rgba(255,255,255,.03); border:1px solid rgba(255,255,255,.08); border-radius:16px; padding:.5rem; overflow:hidden; } -.video-frame video { width:100%; display:block; border-radius:12px; min-height:320px; background:#000; } -.video-frame.large video { min-height:430px; } -.stats-grid { display:grid; grid-template-columns:repeat(4, minmax(0, 1fr)); gap:.75rem; } -.compact-top { margin-top:1rem; } -.mini-stat { background:rgba(255,255,255,.03); border:1px solid rgba(255,255,255,.06); border-radius:14px; padding:.8rem; } -.mini-stat span { display:block; opacity:.72; font-size:.85rem; margin-bottom:.2rem; } -.badge { display:inline-flex; align-items:center; justify-content:center; padding:.35rem .75rem; border-radius:999px; font-weight:700; } -.badge.live { background:#183d27; color:var(--ok); } -.badge.offline { background:#3c1d24; color:var(--bad); } -.stack-actions { display:grid; gap:.75rem; margin-top:1rem; } -.chat-frame { width:100%; min-height:720px; border:none; border-radius:12px; background:#0b0f16; } -@media (max-width:1100px) { .detail-layout, .streamer-layout-fixed { grid-template-columns:1fr; } .stats-grid { grid-template-columns:1fr 1fr; } } -@media (max-width:700px) { .container { padding: 1rem; } .page-header, .topbar { display:grid; grid-template-columns:1fr; } .stats-grid { grid-template-columns:1fr; } } diff --git a/tuxinet-streamhub-v2.1-enterprise/backend/app/static/videooffline.png b/tuxinet-streamhub-v2.1-enterprise/backend/app/static/videooffline.png deleted file mode 100644 index bd6bf7f..0000000 Binary files a/tuxinet-streamhub-v2.1-enterprise/backend/app/static/videooffline.png and /dev/null differ diff --git a/tuxinet-streamhub-v2.1-enterprise/backend/app/templates/admin_dashboard.html b/tuxinet-streamhub-v2.1-enterprise/backend/app/templates/admin_dashboard.html deleted file mode 100644 index 7e5296c..0000000 --- a/tuxinet-streamhub-v2.1-enterprise/backend/app/templates/admin_dashboard.html +++ /dev/null @@ -1,56 +0,0 @@ - -{% extends 'base.html' %} -{% block title %}Admin Dashboard{% endblock %} -{% block content %} - -
-

Streams

- - - - {% for s in streams %} - {% set p = path_map.get(s.slug) %} - {% set live = p and (p.ready or p.online) %} - - - - - - - - - {% endfor %} - -
NavnSlugEjerStatusAktivHandlinger
{{ s.name }}/{{ s.slug }}{{ s.user.display_name }}{{ 'LIVE' if live else 'OFFLINE' }}{{ 'Ja' if s.active else 'Nej' }}Detaljer
-
-
-

Brugere

- - - - {% for u in users %} - - - - - - - - - {% endfor %} - -
NavnBrugernavnE-mailRolleStatusHandlinger
{{ u.display_name }}{{ u.username }}{{ u.email }}{{ u.role }}{{ 'Aktiv' if u.active else 'Deaktiv' }} - {% if u.role == 'streamer' %} -
- -
- {% endif %} -
-
-{% endblock %} diff --git a/tuxinet-streamhub-v2.1-enterprise/backend/app/templates/base.html b/tuxinet-streamhub-v2.1-enterprise/backend/app/templates/base.html deleted file mode 100644 index 45fa1a8..0000000 --- a/tuxinet-streamhub-v2.1-enterprise/backend/app/templates/base.html +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - {% block title %}TuxiNet StreamHub{% endblock %} - - - - -
- - TuxiNet StreamHub - TuxiNet StreamHub - - {% if current_user %} - - {% endif %} -
-
- {% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} - {% for category, message in messages %} -
{{ message }}
- {% endfor %} - {% endif %} - {% endwith %} - {% block content %}{% endblock %} -
- - diff --git a/tuxinet-streamhub-v2.1-enterprise/backend/app/templates/create_streamer.html b/tuxinet-streamhub-v2.1-enterprise/backend/app/templates/create_streamer.html deleted file mode 100644 index 2746532..0000000 --- a/tuxinet-streamhub-v2.1-enterprise/backend/app/templates/create_streamer.html +++ /dev/null @@ -1,29 +0,0 @@ - -{% extends 'base.html' %} -{% block title %}Ny streamer{% endblock %} -{% block content %} - -
-
-

Streamer

-
- - - -

Stream

- - - - - - -
-
-
-{% endblock %} diff --git a/tuxinet-streamhub-v2.1-enterprise/backend/app/templates/force_password_change.html b/tuxinet-streamhub-v2.1-enterprise/backend/app/templates/force_password_change.html deleted file mode 100644 index 9d211ea..0000000 --- a/tuxinet-streamhub-v2.1-enterprise/backend/app/templates/force_password_change.html +++ /dev/null @@ -1,14 +0,0 @@ - -{% extends 'base.html' %} -{% block title %}Skift kode{% endblock %} -{% block content %} -
-

Skift din kode

-

Du skal ændre din midlertidige kode, før du kan fortsætte.

-
- - - -
-
-{% endblock %} diff --git a/tuxinet-streamhub-v2.1-enterprise/backend/app/templates/forgot_password.html b/tuxinet-streamhub-v2.1-enterprise/backend/app/templates/forgot_password.html deleted file mode 100644 index 62996dd..0000000 --- a/tuxinet-streamhub-v2.1-enterprise/backend/app/templates/forgot_password.html +++ /dev/null @@ -1,12 +0,0 @@ - -{% extends 'base.html' %} -{% block title %}Glemt kodeord{% endblock %} -{% block content %} -
-

Glemt kodeord

-
- - -
-
-{% endblock %} diff --git a/tuxinet-streamhub-v2.1-enterprise/backend/app/templates/login.html b/tuxinet-streamhub-v2.1-enterprise/backend/app/templates/login.html deleted file mode 100644 index c5cd7fa..0000000 --- a/tuxinet-streamhub-v2.1-enterprise/backend/app/templates/login.html +++ /dev/null @@ -1,19 +0,0 @@ - -{% extends 'base.html' %} -{% block title %}Login{% endblock %} -{% block body_class %}login-page{% endblock %} -{% block content %} - -{% endblock %} diff --git a/tuxinet-streamhub-v2.1-enterprise/backend/app/templates/reset_password.html b/tuxinet-streamhub-v2.1-enterprise/backend/app/templates/reset_password.html deleted file mode 100644 index 8b26666..0000000 --- a/tuxinet-streamhub-v2.1-enterprise/backend/app/templates/reset_password.html +++ /dev/null @@ -1,13 +0,0 @@ - -{% extends 'base.html' %} -{% block title %}Reset kodeord{% endblock %} -{% block content %} -
-

Nulstil kodeord

-
- - - -
-
-{% endblock %} diff --git a/tuxinet-streamhub-v2.1-enterprise/backend/app/templates/stream_detail_admin.html b/tuxinet-streamhub-v2.1-enterprise/backend/app/templates/stream_detail_admin.html deleted file mode 100644 index 7b00f5a..0000000 --- a/tuxinet-streamhub-v2.1-enterprise/backend/app/templates/stream_detail_admin.html +++ /dev/null @@ -1,71 +0,0 @@ - -{% extends 'base.html' %} -{% block title %}{{ stream.name }}{% endblock %} -{% block content %} - -
-
-

Preview

-
-
{{ public_hls }}
-
-
Bitrate{{ stats.get('bitrate', '-') }}
-
FPS{{ stats.get('fps', '-') }}
-
Video{{ stats.get('video_codec', '-') }}
-
Lyd{{ stats.get('audio_codec', '-') }}
-
Kanaler{{ stats.get('audio_channels', '-') }}
-
Sample rate{{ stats.get('audio_sample_rate', '-') }}
-
-
- -
-
-
-

Forbindelser / sessioner

- - - - {% for row in sessions %} - - {% endfor %} - -
StartStatusIPKilde
{{ row.started_at }}{{ row.status }}{{ row.publisher_ip or '-' }}{{ row.source_type or '-' }}
-
- -
- -{% endblock %} diff --git a/tuxinet-streamhub-v2.1-enterprise/backend/app/templates/streamer_panel.html b/tuxinet-streamhub-v2.1-enterprise/backend/app/templates/streamer_panel.html deleted file mode 100644 index 9c3b638..0000000 --- a/tuxinet-streamhub-v2.1-enterprise/backend/app/templates/streamer_panel.html +++ /dev/null @@ -1,85 +0,0 @@ - -{% extends 'base.html' %} -{% block title %}Streamer Panel{% endblock %} -{% block content %} - -
-
-

Preview

{{ public_hls }}
-
-
-
Bitrate{{ stats.get('bitrate', '-') }}
-
FPS{{ stats.get('fps', '-') }}
-
Video{{ stats.get('video_codec', '-') }}
-
Lyd{{ stats.get('audio_codec', '-') }}
-
Kanaler{{ stats.get('audio_channels', '-') }}
-
Sample rate{{ stats.get('audio_sample_rate', '-') }}
-
Ready{{ 'true' if path_info and path_info.ready else 'false' }}
-
Online{{ 'true' if path_info and path_info.online else 'false' }}
-
-
-

Dine forbindelser

- - - - {% for row in sessions %} - - {% endfor %} - -
StartStatusIP
{{ row.started_at }}{{ row.status }}{{ row.publisher_ip or '-' }}
-
-
- -
- -{% endblock %} diff --git a/tuxinet-streamhub-v2.1-enterprise/backend/app/utils.py b/tuxinet-streamhub-v2.1-enterprise/backend/app/utils.py deleted file mode 100644 index d1d7023..0000000 --- a/tuxinet-streamhub-v2.1-enterprise/backend/app/utils.py +++ /dev/null @@ -1,50 +0,0 @@ - -import secrets -import subprocess -from urllib.parse import urlencode - - -def generate_token(length: int = 32) -> str: - return secrets.token_urlsafe(length)[:length] - - -def generate_password(length: int = 14) -> str: - alphabet = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789!@#$%' - return ''.join(secrets.choice(alphabet) for _ in range(length)) - - -def hls_url(base: str, slug: str) -> str: - return f"{base.rstrip('/')}/{slug}/index.m3u8" - - -def ingest_url_with_query(host: str, slug: str, token: str) -> str: - return f"rtmp://{host}:1935/{slug}?{urlencode({'token': token})}" - - -def ffprobe_stream(url: str) -> dict: - cmd = ['ffprobe', '-v', 'quiet', '-print_format', 'json', '-show_streams', '-show_format', url] - try: - result = subprocess.run(cmd, capture_output=True, text=True, timeout=8, check=True) - except Exception: - return {} - - import json - try: - payload = json.loads(result.stdout or '{}') - except Exception: - return {} - - out = {} - for stream in payload.get('streams', []): - if stream.get('codec_type') == 'video': - out['video_codec'] = stream.get('codec_name') - out['width'] = stream.get('width') - out['height'] = stream.get('height') - out['fps'] = stream.get('avg_frame_rate') - elif stream.get('codec_type') == 'audio': - out['audio_codec'] = stream.get('codec_name') - out['audio_channels'] = stream.get('channels') - out['audio_sample_rate'] = stream.get('sample_rate') - if payload.get('format', {}).get('bit_rate'): - out['bitrate'] = payload['format']['bit_rate'] - return out diff --git a/tuxinet-streamhub-v2.1-enterprise/backend/requirements.txt b/tuxinet-streamhub-v2.1-enterprise/backend/requirements.txt deleted file mode 100644 index ceeab9c..0000000 --- a/tuxinet-streamhub-v2.1-enterprise/backend/requirements.txt +++ /dev/null @@ -1,9 +0,0 @@ - -Flask==3.0.3 -Flask-SQLAlchemy==3.1.1 -Flask-Migrate==4.0.7 -gunicorn==23.0.0 -psycopg[binary]==3.2.1 -requests==2.32.3 -Werkzeug==3.0.3 -itsdangerous==2.2.0 diff --git a/tuxinet-streamhub-v2.1-enterprise/backend/wsgi.py b/tuxinet-streamhub-v2.1-enterprise/backend/wsgi.py deleted file mode 100644 index 0a23b5a..0000000 --- a/tuxinet-streamhub-v2.1-enterprise/backend/wsgi.py +++ /dev/null @@ -1,3 +0,0 @@ -from app import create_app - -app = create_app() diff --git a/tuxinet-streamhub-v2.1-enterprise/caddy/Caddyfile b/tuxinet-streamhub-v2.1-enterprise/caddy/Caddyfile deleted file mode 100644 index 4337c99..0000000 --- a/tuxinet-streamhub-v2.1-enterprise/caddy/Caddyfile +++ /dev/null @@ -1,3 +0,0 @@ -:80 { - reverse_proxy backend:5000 -} diff --git a/tuxinet-streamhub-v2.1-enterprise/docker-compose.yml b/tuxinet-streamhub-v2.1-enterprise/docker-compose.yml deleted file mode 100644 index 713e673..0000000 --- a/tuxinet-streamhub-v2.1-enterprise/docker-compose.yml +++ /dev/null @@ -1,53 +0,0 @@ - -services: - postgres: - image: postgres:16 - container_name: streamhub-postgres - restart: unless-stopped - environment: - POSTGRES_DB: ${POSTGRES_DB} - POSTGRES_USER: ${POSTGRES_USER} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} - TZ: ${TZ} - volumes: - - ./data/postgres:/var/lib/postgresql/data - - mediamtx: - image: bluenviron/mediamtx:1.17.0 - container_name: streamhub-mediamtx - restart: unless-stopped - ports: - - "1935:1935" - - "8888:8888" - - "8889:8889" - - "8189:8189/udp" - - "127.0.0.1:9997:9997" - - "127.0.0.1:9998:9998" - volumes: - - ./mediamtx/mediamtx.yml:/mediamtx.yml:ro - - backend: - build: ./backend - container_name: streamhub-backend - restart: unless-stopped - env_file: - - .env - depends_on: - - postgres - - mediamtx - volumes: - - ./data/logs:/app/logs - - caddy: - image: caddy:2.8 - container_name: streamhub-caddy - restart: unless-stopped - depends_on: - - backend - ports: - - "80:80" - - "443:443" - volumes: - - ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro - - ./data/caddy:/data - - ./data/caddy_config:/config diff --git a/tuxinet-streamhub-v2.1-enterprise/mediamtx/mediamtx.yml b/tuxinet-streamhub-v2.1-enterprise/mediamtx/mediamtx.yml deleted file mode 100644 index 0a3bf72..0000000 --- a/tuxinet-streamhub-v2.1-enterprise/mediamtx/mediamtx.yml +++ /dev/null @@ -1,47 +0,0 @@ - -logLevel: info -logDestinations: [stdout] - -readTimeout: 10s -writeTimeout: 10s -writeQueueSize: 1024 -udpMaxPayloadSize: 1452 - -api: true -apiAddress: :9997 -metrics: true -metricsAddress: :9998 - -rtmp: true -rtmpAddress: :1935 - -hls: true -hlsAddress: :8888 -hlsAlwaysRemux: true -hlsVariant: lowLatency -hlsSegmentCount: 7 -hlsSegmentDuration: 1s -hlsPartDuration: 250ms -hlsMuxerCloseAfter: 30s - -webrtc: true -webrtcAddress: :8889 -webrtcLocalUDPAddress: :8189 -webrtcIPsFromInterfaces: true - -srt: true -srtAddress: :8890 - -authMethod: http -authHTTPAddress: http://backend:5000/api/mediamtx/auth -authHTTPExclude: - - action: api - - action: metrics - - action: pprof - -pathDefaults: - source: publisher - overridePublisher: true - -paths: - all_others: diff --git a/tuxinet-streamhub-v2.1-enterprise/scripts/install.sh b/tuxinet-streamhub-v2.1-enterprise/scripts/install.sh deleted file mode 100644 index 5bb1314..0000000 --- a/tuxinet-streamhub-v2.1-enterprise/scripts/install.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -cp -n .env.example .env || true -echo "Redigér .env før første start"