implement the Dockerized deployment bundle
This commit is contained in:
13
dockerized/.dockerignore
Normal file
13
dockerized/.dockerignore
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
.env
|
||||||
|
runtime/source/.env
|
||||||
|
runtime/source/.git
|
||||||
|
runtime/source/node_modules
|
||||||
|
runtime/source/vendor
|
||||||
|
runtime/source/public/build
|
||||||
|
runtime/source/storage/app/private/recordings
|
||||||
|
runtime/source/storage/framework/cache
|
||||||
|
runtime/source/storage/framework/sessions
|
||||||
|
runtime/source/storage/framework/testing
|
||||||
|
runtime/source/storage/framework/views
|
||||||
|
runtime/source/storage/logs
|
||||||
|
|
||||||
48
dockerized/.env.example
Normal file
48
dockerized/.env.example
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
COMPOSE_PROJECT_NAME=nyone
|
||||||
|
|
||||||
|
# Git source cloned by scripts/deploy.sh into runtime/source before Docker builds.
|
||||||
|
GIT_REPOSITORY_URL=https://github.com/your-org/nyone.git
|
||||||
|
GIT_REF=main
|
||||||
|
|
||||||
|
# Public hostnames. TLS should terminate at your external proxy/CDN/load balancer.
|
||||||
|
PUBLIC_SCHEME=https
|
||||||
|
APP_DOMAIN=nyone.example.com
|
||||||
|
HLS_DOMAIN=nyone-hls.example.com
|
||||||
|
APP_HTTP_PORT=8080
|
||||||
|
RTMP_PUBLIC_PORT=19935
|
||||||
|
|
||||||
|
APP_NAME=Nyone
|
||||||
|
APP_ENV=production
|
||||||
|
APP_DEBUG=false
|
||||||
|
APP_KEY=
|
||||||
|
APP_PREVIOUS_KEYS=
|
||||||
|
APP_LOCALE=en
|
||||||
|
APP_FALLBACK_LOCALE=en
|
||||||
|
APP_FAKER_LOCALE=en_US
|
||||||
|
|
||||||
|
LOG_LEVEL=info
|
||||||
|
MAIL_MAILER=log
|
||||||
|
MAIL_FROM_ADDRESS=hello@example.com
|
||||||
|
MAIL_FROM_NAME=Nyone
|
||||||
|
|
||||||
|
POSTGRES_DB=nyone
|
||||||
|
POSTGRES_USER=nyone
|
||||||
|
POSTGRES_PASSWORD=
|
||||||
|
|
||||||
|
MEDIAMTX_SHARED_SECRET=
|
||||||
|
STREAMING_VIEWER_SYNC_INTERVAL=2
|
||||||
|
STREAMING_VIEWER_COUNT_TTL=10
|
||||||
|
STREAMING_VIEWER_IDENTITY_COOKIE=nyone_visitor_id
|
||||||
|
STREAMING_VIEWER_IDENTITY_COOKIE_LIFETIME_DAYS=365
|
||||||
|
STREAMING_VIEWER_IDENTITY_TTL=86400
|
||||||
|
STREAMING_VIEWER_STATS_POLL_INTERVAL=30000
|
||||||
|
STREAMING_VOD_RETENTION_DAYS=30
|
||||||
|
|
||||||
|
# Optional deployment behavior.
|
||||||
|
SEED_DATABASE=false
|
||||||
|
APP_OPTIMIZE_ON_BOOT=true
|
||||||
|
QUEUE_NAMES=default
|
||||||
|
QUEUE_SLEEP=3
|
||||||
|
QUEUE_TRIES=3
|
||||||
|
QUEUE_MAX_TIME=3600
|
||||||
|
QUEUE_TIMEOUT=90
|
||||||
3
dockerized/.gitignore
vendored
Normal file
3
dockerized/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.env
|
||||||
|
runtime/
|
||||||
|
|
||||||
188
dockerized/docker-compose.yml
Normal file
188
dockerized/docker-compose.yml
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
name: ${COMPOSE_PROJECT_NAME:-nyone-service}
|
||||||
|
|
||||||
|
x-app-environment: &app-environment
|
||||||
|
APP_NAME: ${APP_NAME:-Nyone}
|
||||||
|
APP_ENV: ${APP_ENV:-production}
|
||||||
|
APP_DEBUG: ${APP_DEBUG:-false}
|
||||||
|
APP_KEY: ${APP_KEY:-}
|
||||||
|
APP_PREVIOUS_KEYS: ${APP_PREVIOUS_KEYS:-}
|
||||||
|
APP_URL: ${PUBLIC_SCHEME:-https}://${APP_DOMAIN:-nyone.example.com}
|
||||||
|
APP_LOCALE: ${APP_LOCALE:-en}
|
||||||
|
APP_FALLBACK_LOCALE: ${APP_FALLBACK_LOCALE:-en}
|
||||||
|
APP_FAKER_LOCALE: ${APP_FAKER_LOCALE:-en_US}
|
||||||
|
APP_MAINTENANCE_DRIVER: file
|
||||||
|
BCRYPT_ROUNDS: 12
|
||||||
|
LOG_CHANNEL: stack
|
||||||
|
LOG_STACK: stderr
|
||||||
|
LOG_DEPRECATIONS_CHANNEL: "null"
|
||||||
|
LOG_LEVEL: ${LOG_LEVEL:-info}
|
||||||
|
DB_CONNECTION: pgsql
|
||||||
|
DB_HOST: db
|
||||||
|
DB_PORT: 5432
|
||||||
|
DB_DATABASE: ${POSTGRES_DB:-nyone}
|
||||||
|
DB_USERNAME: ${POSTGRES_USER:-nyone}
|
||||||
|
DB_PASSWORD: ${POSTGRES_PASSWORD:-}
|
||||||
|
SESSION_DRIVER: database
|
||||||
|
SESSION_LIFETIME: 120
|
||||||
|
SESSION_ENCRYPT: "false"
|
||||||
|
SESSION_PATH: /
|
||||||
|
SESSION_DOMAIN: ${SESSION_DOMAIN:-}
|
||||||
|
SESSION_SECURE_COOKIE: ${SESSION_SECURE_COOKIE:-true}
|
||||||
|
BROADCAST_CONNECTION: log
|
||||||
|
FILESYSTEM_DISK: local
|
||||||
|
QUEUE_CONNECTION: database
|
||||||
|
CACHE_STORE: redis
|
||||||
|
REDIS_CLIENT: phpredis
|
||||||
|
REDIS_HOST: redis
|
||||||
|
REDIS_PORT: 6379
|
||||||
|
MAIL_MAILER: ${MAIL_MAILER:-log}
|
||||||
|
MAIL_SCHEME: ${MAIL_SCHEME:-}
|
||||||
|
MAIL_HOST: ${MAIL_HOST:-127.0.0.1}
|
||||||
|
MAIL_PORT: ${MAIL_PORT:-2525}
|
||||||
|
MAIL_USERNAME: ${MAIL_USERNAME:-}
|
||||||
|
MAIL_PASSWORD: ${MAIL_PASSWORD:-}
|
||||||
|
MAIL_FROM_ADDRESS: ${MAIL_FROM_ADDRESS:-hello@example.com}
|
||||||
|
MAIL_FROM_NAME: ${MAIL_FROM_NAME:-Nyone}
|
||||||
|
VITE_APP_NAME: ${APP_NAME:-Nyone}
|
||||||
|
STREAMING_RTMP_INGEST_URL: rtmp://${APP_DOMAIN:-nyone.example.com}:${RTMP_PUBLIC_PORT:-19935}
|
||||||
|
STREAMING_HLS_PUBLIC_URL: ${PUBLIC_SCHEME:-https}://${HLS_DOMAIN:-nyone-hls.example.com}
|
||||||
|
STREAMING_MEDIAMTX_API_URL: http://mediamtx:9997
|
||||||
|
STREAMING_VIEWER_COUNT_CACHE_STORE: redis
|
||||||
|
STREAMING_VIEWER_COUNT_TTL: ${STREAMING_VIEWER_COUNT_TTL:-10}
|
||||||
|
STREAMING_VIEWER_SYNC_INTERVAL: ${STREAMING_VIEWER_SYNC_INTERVAL:-2}
|
||||||
|
STREAMING_VIEWER_IDENTITY_COOKIE: ${STREAMING_VIEWER_IDENTITY_COOKIE:-nyone_visitor_id}
|
||||||
|
STREAMING_VIEWER_IDENTITY_COOKIE_LIFETIME_DAYS: ${STREAMING_VIEWER_IDENTITY_COOKIE_LIFETIME_DAYS:-365}
|
||||||
|
STREAMING_VIEWER_IDENTITY_TTL: ${STREAMING_VIEWER_IDENTITY_TTL:-86400}
|
||||||
|
STREAMING_VIEWER_STATS_POLL_INTERVAL: ${STREAMING_VIEWER_STATS_POLL_INTERVAL:-30000}
|
||||||
|
STREAMING_VOD_RETENTION_DAYS: ${STREAMING_VOD_RETENTION_DAYS:-30}
|
||||||
|
STREAMING_RECORDINGS_PATH: /var/www/html/storage/app/private/recordings
|
||||||
|
MEDIAMTX_SHARED_SECRET: ${MEDIAMTX_SHARED_SECRET:-}
|
||||||
|
APP_DOMAIN: ${APP_DOMAIN:-nyone.example.com}
|
||||||
|
HLS_DOMAIN: ${HLS_DOMAIN:-nyone-hls.example.com}
|
||||||
|
HLS_INTERNAL_PORT: 19988
|
||||||
|
APP_OPTIMIZE_ON_BOOT: ${APP_OPTIMIZE_ON_BOOT:-true}
|
||||||
|
SEED_DATABASE: ${SEED_DATABASE:-false}
|
||||||
|
|
||||||
|
x-app-service: &app-service
|
||||||
|
image: ${APP_IMAGE:-nyone-app:latest}
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: docker/app/Dockerfile
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
<<: *app-environment
|
||||||
|
volumes:
|
||||||
|
- app-storage:/var/www/html/storage
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
redis:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
<<: *app-service
|
||||||
|
environment:
|
||||||
|
<<: *app-environment
|
||||||
|
CONTAINER_ROLE: app
|
||||||
|
ports:
|
||||||
|
- "${APP_HTTP_PORT:-8080}:80"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1/up >/dev/null || exit 1"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 30s
|
||||||
|
|
||||||
|
migrate:
|
||||||
|
<<: *app-service
|
||||||
|
restart: "no"
|
||||||
|
environment:
|
||||||
|
<<: *app-environment
|
||||||
|
CONTAINER_ROLE: migrate
|
||||||
|
profiles:
|
||||||
|
- tools
|
||||||
|
|
||||||
|
queue:
|
||||||
|
<<: *app-service
|
||||||
|
environment:
|
||||||
|
<<: *app-environment
|
||||||
|
CONTAINER_ROLE: queue
|
||||||
|
QUEUE_NAMES: ${QUEUE_NAMES:-default}
|
||||||
|
QUEUE_SLEEP: ${QUEUE_SLEEP:-3}
|
||||||
|
QUEUE_TRIES: ${QUEUE_TRIES:-3}
|
||||||
|
QUEUE_MAX_TIME: ${QUEUE_MAX_TIME:-3600}
|
||||||
|
QUEUE_TIMEOUT: ${QUEUE_TIMEOUT:-90}
|
||||||
|
|
||||||
|
scheduler:
|
||||||
|
<<: *app-service
|
||||||
|
environment:
|
||||||
|
<<: *app-environment
|
||||||
|
CONTAINER_ROLE: scheduler
|
||||||
|
|
||||||
|
viewer-sync:
|
||||||
|
<<: *app-service
|
||||||
|
environment:
|
||||||
|
<<: *app-environment
|
||||||
|
CONTAINER_ROLE: viewer-sync
|
||||||
|
MEDIAMTX_HOST: mediamtx
|
||||||
|
MEDIAMTX_API_PORT: 9997
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
redis:
|
||||||
|
condition: service_healthy
|
||||||
|
mediamtx:
|
||||||
|
condition: service_started
|
||||||
|
|
||||||
|
mediamtx:
|
||||||
|
image: ${MEDIAMTX_IMAGE:-nyone-mediamtx:latest}
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: docker/mediamtx/Dockerfile
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
APP_URL: ${PUBLIC_SCHEME:-https}://${APP_DOMAIN:-nyone.example.com}
|
||||||
|
HLS_PUBLIC_URL: ${PUBLIC_SCHEME:-https}://${HLS_DOMAIN:-nyone-hls.example.com}
|
||||||
|
MEDIAMTX_SHARED_SECRET: ${MEDIAMTX_SHARED_SECRET:-}
|
||||||
|
RTMP_INTERNAL_PORT: 19935
|
||||||
|
HLS_INTERNAL_PORT: 19988
|
||||||
|
ports:
|
||||||
|
- "${RTMP_PUBLIC_PORT:-19935}:19935"
|
||||||
|
volumes:
|
||||||
|
- app-storage:/var/www/html/storage
|
||||||
|
depends_on:
|
||||||
|
app:
|
||||||
|
condition: service_started
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: postgres:17-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: ${POSTGRES_DB:-nyone}
|
||||||
|
POSTGRES_USER: ${POSTGRES_USER:-nyone}
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-}
|
||||||
|
volumes:
|
||||||
|
- postgres-data:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U \"$${POSTGRES_USER}\" -d \"$${POSTGRES_DB}\""]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
command: ["redis-server", "--appendonly", "yes"]
|
||||||
|
volumes:
|
||||||
|
- redis-data:/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "redis-cli", "ping"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
app-storage:
|
||||||
|
postgres-data:
|
||||||
|
redis-data:
|
||||||
75
dockerized/docker/app/Dockerfile
Normal file
75
dockerized/docker/app/Dockerfile
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
|
FROM node:22-bookworm-slim AS assets
|
||||||
|
|
||||||
|
WORKDIR /build
|
||||||
|
|
||||||
|
COPY runtime/source/package.json runtime/source/package-lock.json ./
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
COPY runtime/source/ ./
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
FROM php:8.4-apache-bookworm AS app
|
||||||
|
|
||||||
|
ENV APACHE_DOCUMENT_ROOT=/var/www/html/public \
|
||||||
|
COMPOSER_ALLOW_SUPERUSER=1
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends \
|
||||||
|
ca-certificates \
|
||||||
|
curl \
|
||||||
|
gettext-base \
|
||||||
|
git \
|
||||||
|
gosu \
|
||||||
|
libicu-dev \
|
||||||
|
libcurl4-openssl-dev \
|
||||||
|
libonig-dev \
|
||||||
|
libpq-dev \
|
||||||
|
libzip-dev \
|
||||||
|
netcat-openbsd \
|
||||||
|
unzip \
|
||||||
|
&& docker-php-ext-install -j"$(nproc)" \
|
||||||
|
bcmath \
|
||||||
|
curl \
|
||||||
|
intl \
|
||||||
|
mbstring \
|
||||||
|
opcache \
|
||||||
|
pcntl \
|
||||||
|
pdo_pgsql \
|
||||||
|
pgsql \
|
||||||
|
zip \
|
||||||
|
&& pecl install redis \
|
||||||
|
&& docker-php-ext-enable redis \
|
||||||
|
&& a2enmod headers proxy proxy_http rewrite setenvif \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
|
||||||
|
|
||||||
|
WORKDIR /var/www/html
|
||||||
|
|
||||||
|
COPY runtime/source/composer.json runtime/source/composer.lock ./
|
||||||
|
RUN composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader --no-scripts
|
||||||
|
|
||||||
|
COPY runtime/source/ ./
|
||||||
|
COPY --from=assets /build/public/build ./public/build
|
||||||
|
|
||||||
|
COPY docker/app/apache/nyone.conf.template /etc/apache2/sites-available/nyone.conf.template
|
||||||
|
COPY docker/app/php/production.ini /usr/local/etc/php/conf.d/production.ini
|
||||||
|
COPY docker/app/entrypoint.sh /usr/local/bin/nyone-entrypoint
|
||||||
|
|
||||||
|
RUN composer dump-autoload --optimize --no-dev \
|
||||||
|
&& php artisan package:discover --ansi \
|
||||||
|
&& mkdir -p \
|
||||||
|
storage/app/private/recordings \
|
||||||
|
storage/app/public \
|
||||||
|
storage/framework/cache/data \
|
||||||
|
storage/framework/sessions \
|
||||||
|
storage/framework/views \
|
||||||
|
storage/logs \
|
||||||
|
bootstrap/cache \
|
||||||
|
&& chown -R www-data:www-data storage bootstrap/cache \
|
||||||
|
&& chmod +x /usr/local/bin/nyone-entrypoint
|
||||||
|
|
||||||
|
ENTRYPOINT ["nyone-entrypoint"]
|
||||||
|
CMD ["app"]
|
||||||
43
dockerized/docker/app/apache/nyone.conf.template
Normal file
43
dockerized/docker/app/apache/nyone.conf.template
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
ServerName ${APP_DOMAIN}
|
||||||
|
|
||||||
|
<VirtualHost *:80>
|
||||||
|
ServerName ${APP_DOMAIN}
|
||||||
|
|
||||||
|
DocumentRoot /var/www/html/public
|
||||||
|
DirectoryIndex index.php
|
||||||
|
|
||||||
|
SetEnvIf X-Forwarded-Proto "^https$" HTTPS=on
|
||||||
|
SetEnvIf X-Forwarded-Ssl "^on$" HTTPS=on
|
||||||
|
|
||||||
|
<Directory /var/www/html/public>
|
||||||
|
Options -Indexes +FollowSymLinks
|
||||||
|
AllowOverride All
|
||||||
|
Require all granted
|
||||||
|
</Directory>
|
||||||
|
|
||||||
|
<FilesMatch "\.php$">
|
||||||
|
SetHandler application/x-httpd-php
|
||||||
|
</FilesMatch>
|
||||||
|
|
||||||
|
ErrorLog /proc/self/fd/2
|
||||||
|
CustomLog /proc/self/fd/1 combined
|
||||||
|
</VirtualHost>
|
||||||
|
|
||||||
|
<VirtualHost *:80>
|
||||||
|
ServerName ${HLS_DOMAIN}
|
||||||
|
|
||||||
|
ProxyRequests Off
|
||||||
|
ProxyPreserveHost On
|
||||||
|
|
||||||
|
ProxyPass "/" "http://mediamtx:${HLS_INTERNAL_PORT}/" retry=0
|
||||||
|
ProxyPassReverse "/" "http://mediamtx:${HLS_INTERNAL_PORT}/"
|
||||||
|
|
||||||
|
Header always set Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate"
|
||||||
|
Header always set Access-Control-Allow-Origin "${APP_URL}"
|
||||||
|
Header always set Access-Control-Allow-Methods "GET, HEAD, OPTIONS"
|
||||||
|
Header always set Access-Control-Allow-Headers "Range, Origin, Accept, Content-Type"
|
||||||
|
|
||||||
|
ErrorLog /proc/self/fd/2
|
||||||
|
CustomLog /proc/self/fd/1 combined
|
||||||
|
</VirtualHost>
|
||||||
|
|
||||||
134
dockerized/docker/app/entrypoint.sh
Normal file
134
dockerized/docker/app/entrypoint.sh
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cd /var/www/html
|
||||||
|
|
||||||
|
role="${CONTAINER_ROLE:-${1:-app}}"
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "nyone-entrypoint: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
require_env() {
|
||||||
|
local key="$1"
|
||||||
|
local value="${!key:-}"
|
||||||
|
|
||||||
|
if [[ -z "$value" || "$value" == "change-me"* || "$value" == "base64:change-me"* ]]; then
|
||||||
|
fail "$key is required. Run dockerized/scripts/deploy.sh or set it in dockerized/.env."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for() {
|
||||||
|
local host="$1"
|
||||||
|
local port="$2"
|
||||||
|
local label="$3"
|
||||||
|
local attempts="${4:-60}"
|
||||||
|
|
||||||
|
for ((i = 1; i <= attempts; i++)); do
|
||||||
|
if nc -z "$host" "$port" >/dev/null 2>&1; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
fail "Timed out waiting for $label at $host:$port."
|
||||||
|
}
|
||||||
|
|
||||||
|
artisan() {
|
||||||
|
gosu www-data php artisan "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
render_apache_config() {
|
||||||
|
export APP_DOMAIN HLS_DOMAIN HLS_INTERNAL_PORT APP_URL
|
||||||
|
|
||||||
|
envsubst '${APP_DOMAIN} ${HLS_DOMAIN} ${HLS_INTERNAL_PORT} ${APP_URL}' \
|
||||||
|
< /etc/apache2/sites-available/nyone.conf.template \
|
||||||
|
> /etc/apache2/sites-available/000-default.conf
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare_laravel_paths() {
|
||||||
|
mkdir -p \
|
||||||
|
storage/app/private/recordings \
|
||||||
|
storage/app/public \
|
||||||
|
storage/framework/cache/data \
|
||||||
|
storage/framework/sessions \
|
||||||
|
storage/framework/views \
|
||||||
|
storage/logs \
|
||||||
|
bootstrap/cache
|
||||||
|
|
||||||
|
chown -R www-data:www-data storage bootstrap/cache
|
||||||
|
}
|
||||||
|
|
||||||
|
cache_laravel_bootstrap() {
|
||||||
|
if [[ "${APP_OPTIMIZE_ON_BOOT:-true}" != "true" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
artisan config:clear
|
||||||
|
artisan route:clear
|
||||||
|
artisan view:clear
|
||||||
|
artisan event:clear
|
||||||
|
artisan config:cache
|
||||||
|
artisan route:cache
|
||||||
|
artisan view:cache
|
||||||
|
}
|
||||||
|
|
||||||
|
require_env APP_KEY
|
||||||
|
require_env APP_DOMAIN
|
||||||
|
require_env HLS_DOMAIN
|
||||||
|
require_env MEDIAMTX_SHARED_SECRET
|
||||||
|
require_env DB_DATABASE
|
||||||
|
require_env DB_USERNAME
|
||||||
|
require_env DB_PASSWORD
|
||||||
|
|
||||||
|
if [[ "${WAIT_FOR_SERVICES:-true}" == "true" ]]; then
|
||||||
|
wait_for "${DB_HOST:-db}" "${DB_PORT:-5432}" "PostgreSQL"
|
||||||
|
wait_for "${REDIS_HOST:-redis}" "${REDIS_PORT:-6379}" "Redis"
|
||||||
|
|
||||||
|
if [[ "$role" == "viewer-sync" ]]; then
|
||||||
|
wait_for "${MEDIAMTX_HOST:-mediamtx}" "${MEDIAMTX_API_PORT:-9997}" "MediaMTX API"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
prepare_laravel_paths
|
||||||
|
artisan storage:link --force >/dev/null || true
|
||||||
|
cache_laravel_bootstrap
|
||||||
|
|
||||||
|
case "$role" in
|
||||||
|
app)
|
||||||
|
render_apache_config
|
||||||
|
exec apache2-foreground
|
||||||
|
;;
|
||||||
|
migrate)
|
||||||
|
artisan migrate --force
|
||||||
|
|
||||||
|
if [[ "${SEED_DATABASE:-false}" == "true" ]]; then
|
||||||
|
artisan db:seed --force
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
queue)
|
||||||
|
exec gosu www-data php artisan queue:work "${QUEUE_CONNECTION:-database}" \
|
||||||
|
--queue="${QUEUE_NAMES:-default}" \
|
||||||
|
--sleep="${QUEUE_SLEEP:-3}" \
|
||||||
|
--tries="${QUEUE_TRIES:-3}" \
|
||||||
|
--max-time="${QUEUE_MAX_TIME:-3600}" \
|
||||||
|
--timeout="${QUEUE_TIMEOUT:-90}"
|
||||||
|
;;
|
||||||
|
scheduler)
|
||||||
|
exec gosu www-data php artisan schedule:work
|
||||||
|
;;
|
||||||
|
viewer-sync)
|
||||||
|
exec gosu www-data php artisan streaming:sync-viewer-counts \
|
||||||
|
--isolated \
|
||||||
|
--interval="${STREAMING_VIEWER_SYNC_INTERVAL:-2}"
|
||||||
|
;;
|
||||||
|
artisan)
|
||||||
|
shift || true
|
||||||
|
exec gosu www-data php artisan "$@"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
exec "$@"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
17
dockerized/docker/app/php/production.ini
Normal file
17
dockerized/docker/app/php/production.ini
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
expose_php=Off
|
||||||
|
memory_limit=512M
|
||||||
|
upload_max_filesize=100M
|
||||||
|
post_max_size=100M
|
||||||
|
max_execution_time=120
|
||||||
|
max_input_vars=3000
|
||||||
|
variables_order=EGPCS
|
||||||
|
|
||||||
|
opcache.enable=1
|
||||||
|
opcache.enable_cli=1
|
||||||
|
opcache.memory_consumption=256
|
||||||
|
opcache.interned_strings_buffer=32
|
||||||
|
opcache.max_accelerated_files=20000
|
||||||
|
opcache.validate_timestamps=0
|
||||||
|
opcache.save_comments=1
|
||||||
|
opcache.jit=0
|
||||||
|
|
||||||
17
dockerized/docker/mediamtx/Dockerfile
Normal file
17
dockerized/docker/mediamtx/Dockerfile
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
FROM bluenviron/mediamtx:latest-ffmpeg
|
||||||
|
|
||||||
|
USER root
|
||||||
|
|
||||||
|
RUN (apk add --no-cache curl gettext >/dev/null 2>&1) \
|
||||||
|
|| (apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends ca-certificates curl gettext-base \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*)
|
||||||
|
|
||||||
|
COPY docker/mediamtx/entrypoint.sh /usr/local/bin/nyone-mediamtx-entrypoint
|
||||||
|
COPY docker/mediamtx/mediamtx.yml.template /etc/mediamtx/mediamtx.yml.template
|
||||||
|
|
||||||
|
RUN chmod +x /usr/local/bin/nyone-mediamtx-entrypoint \
|
||||||
|
&& mkdir -p /config /var/www/html/storage/app/private/recordings
|
||||||
|
|
||||||
|
ENTRYPOINT ["nyone-mediamtx-entrypoint"]
|
||||||
|
|
||||||
35
dockerized/docker/mediamtx/entrypoint.sh
Normal file
35
dockerized/docker/mediamtx/entrypoint.sh
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "nyone-mediamtx-entrypoint: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -z "${MEDIAMTX_SHARED_SECRET:-}" ] || [ "${MEDIAMTX_SHARED_SECRET#change-me}" != "$MEDIAMTX_SHARED_SECRET" ]; then
|
||||||
|
fail "MEDIAMTX_SHARED_SECRET is required. Run dockerized/scripts/deploy.sh or set it in dockerized/.env."
|
||||||
|
fi
|
||||||
|
|
||||||
|
: "${APP_URL:?APP_URL is required.}"
|
||||||
|
: "${HLS_PUBLIC_URL:?HLS_PUBLIC_URL is required.}"
|
||||||
|
: "${RTMP_INTERNAL_PORT:=19935}"
|
||||||
|
: "${HLS_INTERNAL_PORT:=19988}"
|
||||||
|
|
||||||
|
mkdir -p /config /var/www/html/storage/app/private/recordings
|
||||||
|
|
||||||
|
export APP_URL HLS_PUBLIC_URL MEDIAMTX_SHARED_SECRET RTMP_INTERNAL_PORT HLS_INTERNAL_PORT
|
||||||
|
|
||||||
|
envsubst '${APP_URL} ${HLS_PUBLIC_URL} ${MEDIAMTX_SHARED_SECRET} ${RTMP_INTERNAL_PORT} ${HLS_INTERNAL_PORT}' \
|
||||||
|
< /etc/mediamtx/mediamtx.yml.template \
|
||||||
|
> /config/mediamtx.yml
|
||||||
|
|
||||||
|
if command -v mediamtx >/dev/null 2>&1; then
|
||||||
|
exec mediamtx /config/mediamtx.yml
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -x /mediamtx ]; then
|
||||||
|
exec /mediamtx /config/mediamtx.yml
|
||||||
|
fi
|
||||||
|
|
||||||
|
fail "MediaMTX binary was not found in the base image."
|
||||||
|
|
||||||
35
dockerized/docker/mediamtx/mediamtx.yml.template
Normal file
35
dockerized/docker/mediamtx/mediamtx.yml.template
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
authMethod: http
|
||||||
|
authHTTPAddress: http://app/internal/mediamtx/auth?secret=${MEDIAMTX_SHARED_SECRET}
|
||||||
|
authHTTPExclude:
|
||||||
|
- action: read
|
||||||
|
- action: playback
|
||||||
|
- action: api
|
||||||
|
- action: metrics
|
||||||
|
- action: pprof
|
||||||
|
|
||||||
|
api: yes
|
||||||
|
apiAddress: :9997
|
||||||
|
|
||||||
|
rtmp: yes
|
||||||
|
rtmpAddress: :${RTMP_INTERNAL_PORT}
|
||||||
|
|
||||||
|
hls: yes
|
||||||
|
hlsAddress: :${HLS_INTERNAL_PORT}
|
||||||
|
hlsVariant: mpegts
|
||||||
|
hlsAllowOrigins:
|
||||||
|
- ${APP_URL}
|
||||||
|
- ${HLS_PUBLIC_URL}
|
||||||
|
|
||||||
|
pathDefaults:
|
||||||
|
record: yes
|
||||||
|
recordPath: /var/www/html/storage/app/private/recordings/%path/%Y-%m-%d_%H-%M-%S-%f
|
||||||
|
|
||||||
|
runOnReady: curl -fsS -X POST "http://app/internal/mediamtx/ready?secret=${MEDIAMTX_SHARED_SECRET}" --data-urlencode "path=$MTX_PATH" --data-urlencode "source_type=$MTX_SOURCE_TYPE" --data-urlencode "source_id=$MTX_SOURCE_ID"
|
||||||
|
runOnReadyRestart: no
|
||||||
|
runOnNotReady: curl -fsS -X POST "http://app/internal/mediamtx/not-ready?secret=${MEDIAMTX_SHARED_SECRET}" --data-urlencode "path=$MTX_PATH" --data-urlencode "source_type=$MTX_SOURCE_TYPE" --data-urlencode "source_id=$MTX_SOURCE_ID"
|
||||||
|
runOnRecordSegmentComplete: curl -fsS -X POST "http://app/internal/mediamtx/recording-complete?secret=${MEDIAMTX_SHARED_SECRET}" --data-urlencode "path=$MTX_PATH" --data-urlencode "segment_path=$MTX_SEGMENT_PATH" --data-urlencode "duration=$MTX_SEGMENT_DURATION"
|
||||||
|
|
||||||
|
paths:
|
||||||
|
all_others:
|
||||||
|
source: publisher
|
||||||
|
|
||||||
184
dockerized/scripts/deploy.sh
Normal file
184
dockerized/scripts/deploy.sh
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
ENV_FILE="$ROOT_DIR/.env"
|
||||||
|
EXAMPLE_ENV_FILE="$ROOT_DIR/.env.example"
|
||||||
|
SOURCE_DIR="$ROOT_DIR/runtime/source"
|
||||||
|
COMPOSE_FILE="$ROOT_DIR/docker-compose.yml"
|
||||||
|
|
||||||
|
info() {
|
||||||
|
printf '[nyone-deploy] %s\n' "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
printf '[nyone-deploy] ERROR: %s\n' "$*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
require_command() {
|
||||||
|
command -v "$1" >/dev/null 2>&1 || fail "$1 is required."
|
||||||
|
}
|
||||||
|
|
||||||
|
env_value() {
|
||||||
|
local key="$1"
|
||||||
|
|
||||||
|
if [ ! -f "$ENV_FILE" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
awk -v key="$key" '
|
||||||
|
index($0, key "=") == 1 {
|
||||||
|
value = substr($0, length(key) + 2)
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
print value
|
||||||
|
}
|
||||||
|
' "$ENV_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
set_env_value() {
|
||||||
|
local key="$1"
|
||||||
|
local value="$2"
|
||||||
|
local escaped
|
||||||
|
|
||||||
|
escaped="$(printf '%s' "$value" | sed 's/[\/&]/\\&/g')"
|
||||||
|
|
||||||
|
if grep -qE "^${key}=" "$ENV_FILE"; then
|
||||||
|
sed -i.bak "s/^${key}=.*/${key}=${escaped}/" "$ENV_FILE"
|
||||||
|
rm -f "$ENV_FILE.bak"
|
||||||
|
else
|
||||||
|
printf '\n%s=%s\n' "$key" "$value" >> "$ENV_FILE"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
is_missing_secret() {
|
||||||
|
local value="$1"
|
||||||
|
|
||||||
|
[ -z "$value" ] || [[ "$value" == "change-me"* ]] || [[ "$value" == "base64:change-me"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
random_secret() {
|
||||||
|
openssl rand -base64 48 | tr -d '\n' | tr '/+' '_-' | tr -d '='
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_env_file() {
|
||||||
|
if [ -f "$ENV_FILE" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
[ -f "$EXAMPLE_ENV_FILE" ] || fail ".env.example was not found."
|
||||||
|
cp "$EXAMPLE_ENV_FILE" "$ENV_FILE"
|
||||||
|
info "Created .env from .env.example."
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_secret() {
|
||||||
|
local key="$1"
|
||||||
|
local value
|
||||||
|
|
||||||
|
value="$(env_value "$key")"
|
||||||
|
|
||||||
|
if is_missing_secret "$value"; then
|
||||||
|
set_env_value "$key" "$(random_secret)"
|
||||||
|
info "Generated $key."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
require_config_value() {
|
||||||
|
local key="$1"
|
||||||
|
local placeholder="${2:-}"
|
||||||
|
local value
|
||||||
|
|
||||||
|
value="$(env_value "$key")"
|
||||||
|
|
||||||
|
[ -n "$value" ] || fail "Set $key in $ENV_FILE."
|
||||||
|
|
||||||
|
if [ -n "$placeholder" ] && [ "$value" = "$placeholder" ]; then
|
||||||
|
fail "Replace the placeholder $key in $ENV_FILE."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_app_key() {
|
||||||
|
local value
|
||||||
|
|
||||||
|
value="$(env_value APP_KEY)"
|
||||||
|
|
||||||
|
if is_missing_secret "$value"; then
|
||||||
|
set_env_value APP_KEY "base64:$(openssl rand -base64 32 | tr -d '\n')"
|
||||||
|
info "Generated APP_KEY."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
clone_or_update_source() {
|
||||||
|
local repository_url="$1"
|
||||||
|
local git_ref="$2"
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "$SOURCE_DIR")"
|
||||||
|
|
||||||
|
if [ -d "$SOURCE_DIR/.git" ]; then
|
||||||
|
info "Updating existing source checkout."
|
||||||
|
git -C "$SOURCE_DIR" fetch --prune --tags origin
|
||||||
|
|
||||||
|
if git -C "$SOURCE_DIR" rev-parse --verify --quiet "origin/$git_ref" >/dev/null; then
|
||||||
|
git -C "$SOURCE_DIR" checkout -B "$git_ref" "origin/$git_ref"
|
||||||
|
else
|
||||||
|
git -C "$SOURCE_DIR" checkout "$git_ref"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -e "$SOURCE_DIR" ]; then
|
||||||
|
fail "$SOURCE_DIR exists but is not a Git checkout. Move it away or remove it before deploying."
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "Cloning application repository."
|
||||||
|
git clone "$repository_url" "$SOURCE_DIR"
|
||||||
|
git -C "$SOURCE_DIR" checkout "$git_ref"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_compose() {
|
||||||
|
docker compose --env-file "$ENV_FILE" -f "$COMPOSE_FILE" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
require_command git
|
||||||
|
require_command docker
|
||||||
|
require_command openssl
|
||||||
|
|
||||||
|
ensure_env_file
|
||||||
|
ensure_app_key
|
||||||
|
ensure_secret POSTGRES_PASSWORD
|
||||||
|
ensure_secret MEDIAMTX_SHARED_SECRET
|
||||||
|
|
||||||
|
GIT_REPOSITORY_URL="$(env_value GIT_REPOSITORY_URL)"
|
||||||
|
GIT_REF="$(env_value GIT_REF)"
|
||||||
|
|
||||||
|
require_config_value GIT_REPOSITORY_URL "https://github.com/your-org/nyone.git"
|
||||||
|
require_config_value APP_DOMAIN "nyone.example.com"
|
||||||
|
require_config_value HLS_DOMAIN "nyone-hls.example.com"
|
||||||
|
|
||||||
|
if [ "$(env_value APP_DOMAIN)" = "$(env_value HLS_DOMAIN)" ]; then
|
||||||
|
fail "APP_DOMAIN and HLS_DOMAIN must be different because this deployment uses separate Apache virtual hosts."
|
||||||
|
fi
|
||||||
|
|
||||||
|
GIT_REF="${GIT_REF:-main}"
|
||||||
|
|
||||||
|
clone_or_update_source "$GIT_REPOSITORY_URL" "$GIT_REF"
|
||||||
|
|
||||||
|
info "Validating Compose configuration."
|
||||||
|
run_compose config >/dev/null
|
||||||
|
|
||||||
|
info "Building Docker images."
|
||||||
|
run_compose build
|
||||||
|
|
||||||
|
info "Starting database and Redis."
|
||||||
|
run_compose up -d db redis
|
||||||
|
|
||||||
|
info "Running migrations."
|
||||||
|
run_compose --profile tools run --rm migrate
|
||||||
|
|
||||||
|
info "Starting background workers."
|
||||||
|
run_compose up -d --remove-orphans app mediamtx queue scheduler viewer-sync
|
||||||
|
|
||||||
|
info "Current container status:"
|
||||||
|
run_compose ps
|
||||||
Reference in New Issue
Block a user