Compare commits

..

3 Commits

Author SHA1 Message Date
lebaudantoine 02f8bcba12 wip explicit minio hook 2026-09-15 14:13:52 +02:00
lebaudantoine 92299fe008 wip user id 2026-09-15 13:51:07 +02:00
lebaudantoine bda97a9a61 🐛(frontend) fix file permissions in the Docker image
Incorrect file permissions in the frontend Docker image caused
problems when running the project, and were surfaced by @briquet
while setting it up with Podman.

Adjust the ownership and permissions applied during the build so
the image works cleanly under Docker and Podman alike.
2026-09-14 22:11:40 +02:00
43 changed files with 630 additions and 714 deletions
-3
View File
@@ -86,6 +86,3 @@ docker/livekit/rootCA.pem
# Frontend rollup-plugin-visualizer
/src/frontend/rollup-plugin-visualizer/*
# NixOS
.devenv
-4
View File
@@ -11,8 +11,6 @@ and this project adheres to
### Added
- ✨(backend) make the LiveKit default video codec configurable
- 🔧(dev) add support for Bureautix workstations
- ✨(frontend) switch frontend images to Caddy and proxy recording/file downloads through it, removing the NGINX Ingress auth annotations dependency
### Changed
@@ -27,8 +25,6 @@ and this project adheres to
### Fixed
- 🐛(backend) report the app release to Sentry instead of "NA"
- 🐛(frontend) play the waiting room notification sound on every arrival
- 🐛(backend) acknowledge unknown LiveKit webhook events instead of 422
- 🔒️(backend) enforce display name setting on rename API
- 🔒️(backend) reject inactive users in resource server backend
+7 -2
View File
@@ -36,9 +36,14 @@ DB_PORT = 5432
# -- Docker
# Get the current user ID to use for docker run and docker exec commands
ifneq ($(findstring podman,$(DOCKER_HOST)),)
DOCKER_UID = 0
DOCKER_GID = 0
else
DOCKER_UID = $(shell id -u)
DOCKER_GID = $(shell id -g)
DOCKER_USER = $(DOCKER_UID):$(DOCKER_GID)
endif
DOCKER_USER ?= $(DOCKER_UID):$(DOCKER_GID)
COMPOSE = DOCKER_USER=$(DOCKER_USER) docker compose
COMPOSE_EXEC = $(COMPOSE) exec
COMPOSE_EXEC_APP = $(COMPOSE_EXEC) app-dev
@@ -292,7 +297,7 @@ shell: ## connect to database shell
# -- Database
dbshell: ## connect to database shell
@$(COMPOSE_EXEC_APP) python manage.py dbshell
docker compose exec app-dev python manage.py dbshell
.PHONY: dbshell
resetdb: FLUSH_ARGS ?=
+7 -3
View File
@@ -5,7 +5,7 @@ set -eo pipefail
REPO_DIR="$(cd "$( dirname "${BASH_SOURCE[0]}" )/.." && pwd)"
UNSET_USER=0
export COMPOSE_FILE="${COMPOSE_FILE:-${REPO_DIR}/compose.yml}"
COMPOSE_FILE="${REPO_DIR}/compose.yml"
COMPOSE_PROJECT="meet"
@@ -25,8 +25,11 @@ function _set_user() {
return
fi
# USER_ID = USER_ID or `id -u` if USER_ID is not set
USER_ID=${USER_ID:-$(id -u)}
# USER_ID = USER_ID or the engine-appropriate default if USER_ID is not set.
case "${DOCKER_HOST:-}" in
*podman*) USER_ID=${USER_ID:-0} ;;
*) USER_ID=${USER_ID:-$(id -u)} ;;
esac
echo "🙋(user) ID: ${USER_ID}"
}
@@ -42,6 +45,7 @@ function _docker_compose() {
echo "🐳(compose) project: '${COMPOSE_PROJECT}' file: '${COMPOSE_FILE}'"
docker compose \
-p "${COMPOSE_PROJECT}" \
-f "${COMPOSE_FILE}" \
--project-directory "${REPO_DIR}" \
"$@"
}
Executable
+6
View File
@@ -0,0 +1,6 @@
#!/usr/bin/env bash
# shellcheck source=bin/_config.sh
source "$(dirname "${BASH_SOURCE[0]}")/_config.sh"
_docker_compose "$@"
+1 -5
View File
@@ -40,11 +40,7 @@ services:
minio:
condition: service_healthy
restart: true
entrypoint: >
sh -c "
/usr/bin/mc alias set meet http://minio:9000 meet password && \
/usr/bin/mc mb meet/meet-media-storage && \
exit 0;"
entrypoint: ["/bin/sh", "-c", "mc alias set meet http://minio:9000 meet password && mc mb --ignore-existing meet/meet-media-storage"]
app-dev:
build:
-47
View File
@@ -1,47 +0,0 @@
{
"nodes": {
"devenv": {
"locked": {
"dir": "src/modules",
"lastModified": 1778705847,
"narHash": "sha256-EQnZCy7r4VMO6KDoytxHBa0mFbM1D9g1kaDfs/s0YZA=",
"ref": "refs/tags/v2.1.2",
"rev": "ea3d94ac9d6bf6a1313773170122ca4e2ef5a0be",
"revCount": 6569,
"type": "git",
"url": "https://github.com/cachix/devenv"
},
"original": {
"dir": "src/modules",
"ref": "refs/tags/v2.1.2",
"type": "git",
"url": "https://github.com/cachix/devenv"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1789542786,
"narHash": "sha256-ajQuqcxnj6RYnwCjA/9FTCnrdR2+BDzstE8UembXf60=",
"ref": "nixos-26.05",
"rev": "4c7870105e7f1fdf9c48688c8d7efc21abf0688a",
"shallow": true,
"type": "git",
"url": "https://github.com/NixOS/nixpkgs"
},
"original": {
"ref": "nixos-26.05",
"shallow": true,
"type": "git",
"url": "https://github.com/NixOS/nixpkgs"
}
},
"root": {
"inputs": {
"devenv": "devenv",
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}
-265
View File
@@ -1,265 +0,0 @@
# =============================================================================
# devenv.nix — La Suite Meet ("Visio") developer environment
# =============================================================================
{
pkgs,
lib,
config,
...
}:
let
python = pkgs.python313;
nodejs = pkgs.nodejs_22;
backendDir = "src/backend";
agentsDir = "src/agents";
summaryDir = "src/summary";
frontendDir = "src/frontend";
readDotEnv =
file:
let
lines = lib.splitString "\n" (builtins.readFile file);
unquote =
v:
let
len = builtins.stringLength v;
in
if len >= 2 && lib.hasPrefix "\"" v && lib.hasSuffix "\"" v then
builtins.substring 1 (len - 2) v
else if len >= 2 && lib.hasPrefix "'" v && lib.hasSuffix "'" v then
builtins.substring 1 (len - 2) v
else
v;
parseLine =
line:
let
m = builtins.match "[ \t]*([A-Za-z_][A-Za-z0-9_]*)[ \t]*=[ \t]*(.*)" line;
in
if m == null then null else { name = builtins.elemAt m 0; value = unquote (builtins.elemAt m 1); };
in
builtins.listToAttrs (builtins.filter (x: x != null) (map parseLine lines));
# Reuse existing .env
dotEnv =
(readDotEnv ./env.d/development/common.dist)
// (readDotEnv ./env.d/development/postgresql.dist);
sharedEnv = builtins.removeAttrs dotEnv [ "PYTHONPATH" ]; # only makes sense inside the backend container.
in
{
options.meet = {
agents.enable = lib.mkEnableOption "tooling for the LiveKit agents in src/agents";
summary.enable = lib.mkEnableOption "tooling for the summary service in src/summary";
k8s.enable = lib.mkEnableOption "Kubernetes dev utilities";
};
config = {
# Profile can be activated with devenv --profile <profile> shell
profiles = {
agents.module = {
meet.agents.enable = true;
};
summary.module = {
meet.summary.enable = true;
};
k8s.module = {
meet.k8s.enable = true;
};
};
languages.python = {
enable = true;
package = python;
directory = backendDir;
manylinux.enable = pkgs.stdenv.hostPlatform.isLinux;
libraries = [
"${config.devenv.dotfile}/profile"
pkgs.file
pkgs.zlib
pkgs.libffi
pkgs.openssl
];
uv.enable = true;
uv.sync.enable = false;
venv.enable = false;
lsp.enable = true;
};
languages.javascript = {
enable = true;
package = nodejs;
directory = frontendDir;
npm.enable = true;
yarn.enable = true;
corepack.enable = false;
};
languages.typescript.enable = false;
languages.nix.enable = true;
packages =
with pkgs;
[
gnumake
file
shared-mime-info
gettext
postgresql_16
git
curl
jq
podman
podman-compose
docker-client
]
# -- LiveKit agents
++ lib.optionals config.meet.agents.enable [
glib
portaudio
livekit-cli
]
# -- summary service
++ lib.optionals config.meet.summary.enable [
redis
]
# -- Kubernetes tools
++ lib.optionals config.meet.k8s.enable [
kubectl
kubernetes-helm
helmfile
tilt
kind
mkcert
];
env = sharedEnv // {
UV_LINK_MODE = "copy";
PYTHONDONTWRITEBYTECODE = "1";
PYTHONUNBUFFERED = "1";
UV_PROJECT_ENVIRONMENT = lib.mkForce ".venv";
COMPOSE_PROJECT_NAME = "meet";
DJANGO_DATA_DIR = "${config.devenv.root}/data";
# Database / Pgsql
DB_HOST = "127.0.0.1";
DB_PORT = "15432";
PGHOST = "127.0.0.1";
PGPORT = "15432";
PGDATABASE = sharedEnv.DB_NAME;
PGUSER = sharedEnv.DB_USER;
PGPASSWORD = sharedEnv.DB_PASSWORD;
REDIS_URL = "redis://127.0.0.1:6379/1";
CELERY_BROKER_URL = "redis://127.0.0.1:6379/0";
# S3 / MinIO
AWS_S3_ENDPOINT_URL = "http://127.0.0.1:9000";
# OIDC
OIDC_OP_JWKS_ENDPOINT = "http://localhost:8083/realms/meet/protocol/openid-connect/certs";
OIDC_OP_TOKEN_ENDPOINT = "http://localhost:8083/realms/meet/protocol/openid-connect/token";
OIDC_OP_USER_ENDPOINT = "http://localhost:8083/realms/meet/protocol/openid-connect/userinfo";
OIDC_OP_INTROSPECTION_ENDPOINT = "http://localhost:8083/realms/meet/protocol/openid-connect/token/introspect";
# summary service
SUMMARY_SERVICE_ENDPOINT = "http://127.0.0.1:8001/api/v2/async-jobs/transcribe/";
SUMMARY_SERVICE_VERSION = "2";
# Mail
DJANGO_EMAIL_HOST = "127.0.0.1";
};
scripts = {
meet-venv = {
description = "Create/refresh meet uv virtualenvs for backend, agents and summary";
exec = ''
set -euo pipefail
cd "$DEVENV_ROOT"
echo "==> ${backendDir} (uv sync --locked, dependency-groups)"
( cd "${backendDir}" && uv sync --locked --all-groups )
echo "==> ${agentsDir} (uv sync --locked --all-extras)"
( cd "${agentsDir}" && uv sync --locked --all-extras )
echo "==> ${summaryDir} (uv sync --locked --all-extras)"
( cd "${summaryDir}" && uv sync --locked --all-extras )
echo
echo "Synced the following virtualenvs successfully:"
echo " ${backendDir}/.venv"
echo " ${agentsDir}/.venv"
echo " ${summaryDir}/.venv"
'';
};
};
enterShell = ''
# Make podman socket accessible in order to launch regular docker commands.
# Set MEET_PODMAN_SOCKET=0 to keep the DOCKER_HOST of the calling environment.
case "''${MEET_PODMAN_SOCKET:-1}" in
0|false|no|off) ;;
*)
_rundir="''${XDG_RUNTIME_DIR:-/run/user/$(id -u)}"
export DOCKER_HOST="unix://$_rundir/podman/podman.sock"
unset _rundir
;;
esac
# Compose files to merge
_compose_dir="${config.devenv.root}/docker/compose.d"
_compose_files="${config.devenv.root}/compose.yml"
export DOCKER_USER="$(id -u):$(id -g)"
case "''${DOCKER_HOST:-}" in
*podman*)
_compose_files="$_compose_files:$_compose_dir/compose.podman.yml"
# Build images with Podman/Buildah rather than BuildKit. `docker
# compose build` otherwise has buildx boot a moby/buildkit container,
# and that container lands in its own network namespace with neither
# the proxy in its environment nor any route to it.
# Buildah has neither problem: base images are resolved by the Podman systemd
# service, which inherits the proxy from its systemd socket activated unit, and
# RUN steps execute in the *host* network namespace
export DOCKER_BUILDKIT=0
export COMPOSE_BAKE=false
;;
esac
# Apply Bureautix override
if [ -n "''${http_proxy:-}" ]; then
_compose_files="$_compose_files:$_compose_dir/compose.bureautix.yml"
fi
export COMPOSE_FILE="$_compose_files"
unset _compose_dir _compose_files
# Make binaries accessible
for _d in \
"$DEVENV_ROOT/${backendDir}/.venv/bin" \
"$DEVENV_ROOT/${agentsDir}/.venv/bin" \
"$DEVENV_ROOT/${summaryDir}/.venv/bin" \
"$DEVENV_ROOT/${frontendDir}/node_modules/.bin"
do
[ -d "$_d" ] && export PATH="$_d:$PATH"
done
unset _d
'';
};
}
-5
View File
@@ -1,5 +0,0 @@
inputs:
nixpkgs:
url: git+https://github.com/NixOS/nixpkgs?ref=nixos-26.05&shallow=1
devenv:
url: git+https://github.com/cachix/devenv?ref=refs/tags/v2.1.2&dir=src/modules
-42
View File
@@ -1,42 +0,0 @@
# Shared Caddy snippet: proxies recording/file downloads to object storage after
# checking authorization with the backend
#
# Env vars (all optional, fall back to local dev defaults): BACKEND_INTERNAL_HOST,
# BACKEND_INTERNAL_PORT, MEDIA_STORAGE_HOST, MEDIA_STORAGE_PROTOCOL, MEDIA_STORAGE_PORT, AWS_STORAGE_BUCKET_NAME.
handle /media/files/* {
route {
forward_auth http://{$BACKEND_INTERNAL_HOST:localhost}:{$BACKEND_INTERNAL_PORT:8000} {
uri /api/v1.0/files/media-auth/
copy_headers Authorization X-Amz-Date X-Amz-Content-Sha256
header_up X-Original-URL {http.request.uri}
# Backend has SECURE_SSL_REDIRECT: without this the auth subrequest is
# 301'd to https, the browser follows it to media-auth (no X-Original-URL) and 403s.
header_up X-Forwarded-Proto https
}
uri replace /media/ /{$AWS_STORAGE_BUCKET_NAME:meet-media-storage}/ 1
reverse_proxy {$MEDIA_STORAGE_PROTOCOL:http}://{$MEDIA_STORAGE_HOST:localhost}:{$MEDIA_STORAGE_PORT:9000} {
header_up Host {$MEDIA_STORAGE_HOST:localhost}:{$MEDIA_STORAGE_PORT:9000}
header_down -Content-Disposition
header_down Content-Disposition attachment
}
}
}
# Recordings media - kept generic (/media/*) to match the existing ingress path.
handle /media/* {
route {
forward_auth http://{$BACKEND_INTERNAL_HOST:localhost}:{$BACKEND_INTERNAL_PORT:8000} {
uri /api/v1.0/recordings/media-auth/
copy_headers Authorization X-Amz-Date X-Amz-Content-Sha256
header_up X-Original-URL {http.request.uri}
header_up X-Forwarded-Proto https
}
uri replace /media/ /{$AWS_STORAGE_BUCKET_NAME:meet-media-storage}/ 1
reverse_proxy {$MEDIA_STORAGE_PROTOCOL:http}://{$MEDIA_STORAGE_HOST:localhost}:{$MEDIA_STORAGE_PORT:9000} {
header_up Host {$MEDIA_STORAGE_HOST:localhost}:{$MEDIA_STORAGE_PORT:9000}
header_down -Content-Disposition
header_down Content-Disposition attachment
}
}
}
-48
View File
@@ -1,48 +0,0 @@
# Bureautix proxy overrides
#
# Builds submitted through the Docker API of the Podman service get none of
# the proxy variables in their RUN steps. We need to pass the http_proxy explicitely
# otherwise all connections fail during the build.
x-proxy-vars: &proxy-vars
http_proxy: ${http_proxy:-}
https_proxy: ${https_proxy:-}
no_proxy: ${no_proxy:-}
services:
app:
build:
args:
<<: *proxy-vars
app-dev:
build:
args:
<<: *proxy-vars
frontend:
build:
args:
<<: *proxy-vars
metadata-collector-dev:
build:
args:
<<: *proxy-vars
multi-user-transcriber-dev:
build:
args:
<<: *proxy-vars
app-summary-dev:
build:
args:
<<: *proxy-vars
celery-summary-transcribe:
build:
args:
<<: *proxy-vars
celery-summary-summarize:
build:
args:
<<: *proxy-vars
# The local proxy listens on 8080 and collides with Keycloak's published admin port.
keycloak:
ports: !override
- "8081:8080"
-33
View File
@@ -1,33 +0,0 @@
# Rootless Podman override for compose.yml.
#
# Rootless Podman maps container UID 0 to the host user and every other
# container UID to a subuid that owns nothing in the worktree. Meet compose.ymlruns
# its containers as DOCKER_USER=$(id -u):$(id -g), which would land on such a
# subuid and make every bind mount effectively read-only.
#
# `userns_mode: keep-id` maps the host user to the same UID and GID inside the
# container instead, so DOCKER_USER keeps its Docker value and files written
# through a bind mount are owned by the host user on both sides.
#
# Only the services that mount the worktree and run as DOCKER_USER are listed.
x-keep-id: &keep-id
userns_mode: keep-id
services:
app-dev:
<<: *keep-id
celery-dev:
<<: *keep-id
minio:
<<: *keep-id
node:
<<: *keep-id
crowdin:
<<: *keep-id
metadata-collector-dev:
<<: *keep-id
multi-user-transcriber-dev:
<<: *keep-id
app-summary-dev:
<<: *keep-id
-75
View File
@@ -1,75 +0,0 @@
# Global options
{
auto_https off
admin off
# replace_response can't process compressed bodies, so it must run before encode.
order replace before encode
}
:{$PORT} {
root * /usr/share/nginx/html
encode gzip
route {
import /etc/caddy/media-proxy.caddy
handle /.well-known/windows-app-web-link {
header Content-Type "application/json"
header Content-Disposition "attachment; filename=windows-app-web-link"
file_server
}
# Manifest — fetched, never iframed
handle /addons/outlook/manifest.xml {
header Access-Control-Allow-Origin "*"
header Cache-Control "no-cache, no-store, must-revalidate"
header X-Frame-Options "DENY"
header Content-Security-Policy "frame-ancestors 'none'"
file_server
}
handle /addons/outlook/assets/* {
header Cache-Control "public, max-age=2592000, immutable"
header Access-Control-Allow-Origin "*"
header Vary "Origin"
file_server
}
# Outlook add-on pages: per-request CSP nonce, mirrors the Office.js/config.js
# script tags baked into the build with a literal NONCE_PLACEHOLDER string.
handle /addons/outlook/* {
header Cache-Control "no-cache, no-store, must-revalidate"
header Pragma "no-cache"
header Expires 0
header Content-Security-Policy "default-src 'self'; upgrade-insecure-requests; frame-ancestors https://*.live.com https://*.office.com https://*.microsoft.com https://*.office365.com https://*.sharepoint.com; script-src 'nonce-{http.request.uuid}' 'strict-dynamic'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; connect-src 'self' https://*.live.com https://*.office.com https://*.microsoft.com https://*.office365.com https://*.sharepoint.com; frame-src 'none'; object-src 'none'; base-uri 'none'"
replace NONCE_PLACEHOLDER {http.request.uuid}
try_files {path} /index.html
file_server
}
# Vite fingerprints everything under /assets, so a given URL's bytes never
# change: cache it forever. A new build emits new hashed URLs.
@immutable path /assets/*
header @immutable Cache-Control "public, max-age=2592000, immutable"
# The SPA shell and other non-fingerprinted files must revalidate every
# load, or a deploy's new asset hashes only show up after a hard refresh.
@revalidate not path /assets/*
header @revalidate {
Cache-Control "no-cache, no-store, must-revalidate"
Pragma "no-cache"
Expires 0
}
try_files {path} /index.html
file_server
}
handle_errors {
@spa_404 expression `{err.status_code} == 404`
handle @spa_404 {
rewrite * /index.html
file_server
}
}
}
+14 -12
View File
@@ -52,17 +52,19 @@ COPY ./src/addons/outlook/ .
RUN npx webpack --mode production
# ---- Caddy builder image ----
FROM caddy:2.11.4-builder AS caddy-builder
RUN xcaddy build --with github.com/caddyserver/replace-response
RUN apk add --no-cache libcap && \
setcap -r /usr/bin/caddy
# ---- Front-end image ----
FROM gcr.io/distroless/static-debian12:nonroot AS frontend-production
FROM nginxinc/nginx-unprivileged:1.30.4-alpine3.24 AS frontend-production
COPY --from=caddy-builder /usr/bin/caddy /usr/bin/caddy
USER root
RUN apk del curl
USER nginx
USER nginx
# Un-privileged user running the application
ARG DOCKER_USER
USER ${DOCKER_USER}
COPY --from=meet-builder \
/home/frontend/dist \
@@ -72,9 +74,9 @@ COPY --from=addons-builder \
/home/addons/outlook/dist \
/usr/share/nginx/html/addons/outlook
COPY ./docker/dinum-frontend/Caddyfile /etc/caddy/Caddyfile
COPY ./docker/caddy/media-proxy.caddy /etc/caddy/media-proxy.caddy
COPY ./docker/dinum-frontend/nginx/default.conf /etc/nginx/conf.d
COPY ./docker/files/usr/local/bin/entrypoint /usr/local/bin/entrypoint
ENV PORT=8080
ENTRYPOINT [ "/usr/local/bin/entrypoint" ]
ENTRYPOINT ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
CMD ["nginx", "-g", "daemon off;"]
+90
View File
@@ -0,0 +1,90 @@
server {
listen 8080;
server_name localhost;
server_tokens off;
root /usr/share/nginx/html;
location = /.well-known/windows-app-web-link {
default_type application/json;
alias /usr/share/nginx/html/.well-known/windows-app-web-link;
add_header Content-Disposition "attachment; filename=windows-app-web-link";
}
# Manifest — fetched, never iframed
location = /addons/outlook/manifest.xml {
alias /usr/share/nginx/html/addons/outlook/manifest.xml;
add_header Access-Control-Allow-Origin "*";
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header X-Frame-Options "DENY";
add_header Content-Security-Policy "frame-ancestors 'none'";
}
location = /addons/outlook/assets/ {
return 404;
}
location ~* ^/addons/outlook/assets/(.+\.(?:css|js|json|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot))/?$ {
root /usr/share/nginx/html;
expires 30d;
add_header Cache-Control "public, max-age=2592000, immutable" always;
add_header Access-Control-Allow-Origin "*";
add_header Vary "Origin" always;
}
location = /addons/outlook/ {
return 404;
}
location ~ ^/addons/outlook(/.*)?$ {
alias /usr/share/nginx/html/addons/outlook$1;
error_page 404 =200 /index.html;
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache" always;
add_header Expires 0 always;
set $ms_domains "https://*.live.com https://*.office.com https://*.microsoft.com https://*.office365.com https://*.sharepoint.com";
set $nonce $request_id;
set $csp "default-src 'self'; upgrade-insecure-requests; ";
set $csp "${csp}frame-ancestors ${ms_domains}; ";
set $csp "${csp}script-src 'nonce-${nonce}' 'strict-dynamic'; ";
set $csp "${csp}style-src 'self' 'unsafe-inline'; ";
set $csp "${csp}img-src 'self' data:; ";
set $csp "${csp}font-src 'self' data:; ";
set $csp "${csp}connect-src 'self' ${ms_domains}; ";
set $csp "${csp}frame-src 'none'; ";
set $csp "${csp}object-src 'none'; ";
set $csp "${csp}base-uri 'none'; ";
add_header Content-Security-Policy $csp;
sub_filter 'NONCE_PLACEHOLDER' $nonce;
sub_filter_once off;
}
location ^~ /assets/mediapipe/wasm/ {
expires 30d;
add_header Cache-Control "public, max-age=2592000";
}
# Serve static files with caching
location ~* ^/assets/.*\.(css|js|json|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, max-age=2592000";
}
# Serve static files
location / {
try_files $uri $uri/ /index.html;
# Add no-cache headers
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache"; # HTTP 1.0 header for backward compatibility
add_header Expires 0;
}
# Optionally, handle 404 errors by redirecting to index.html
error_page 404 =200 /index.html;
}
@@ -21,10 +21,3 @@ turn:
- 192.168.0.0/16
- 172.16.0.0/12
rtc:
node_ip: 127.0.0.1
advertise_internal_ip: true
udp_port: 7882
tcp_port: 7881
use_external_ip: false
+2 -3
View File
@@ -1,8 +1,7 @@
FROM python:3.14.6-slim AS base
# Install system dependencies required by LiveKit, fetching packages over HTTPS only for Bureautix proxy
RUN sed -i "s|^URIs: http://|URIs: https://|" /etc/apt/sources.list.d/debian.sources \
&& apt-get update && apt-get install -y --no-install-recommends \
# Install system dependencies required by LiveKit
RUN apt-get update && apt-get install -y \
libglib2.0-0 \
libgobject-2.0-0 \
&& rm -rf /var/lib/apt/lists/*
@@ -117,11 +117,9 @@ def test_start_subtitle_invalid_token():
assert response.json() == {"detail": "Invalid LiveKit token: Not enough segments"}
def test_start_subtitle_disabled_by_default(mock_livekit_token, settings):
def test_start_subtitle_disabled_by_default(mock_livekit_token):
"""Test that subtitle functionality is disabled when feature flag is off."""
settings.ROOM_SUBTITLE_ENABLED = False
room = RoomFactory()
user = UserFactory()
client = APIClient()
@@ -1,47 +0,0 @@
"""Unit tests for the get_release settings helper."""
import re
import pytest
from meet.settings import get_release
@pytest.fixture(name="base_dir")
def fixture_empty_base_dir(tmp_path, monkeypatch):
"""Point get_release at an empty directory."""
monkeypatch.setattr("meet.settings.BASE_DIR", str(tmp_path))
return tmp_path
def test_get_release_reads_project_pyproject():
"""Should return the semantic version of the backend's pyproject.toml."""
assert re.fullmatch(r"\d+\.\d+\.\d+", get_release())
def test_get_release_reads_pyproject_version(base_dir):
"""Should return the version declared in the [project] table."""
(base_dir / "pyproject.toml").write_text(
'[project]\nname = "meet"\nversion = "1.2.3"\n', encoding="utf-8"
)
assert get_release() == "1.2.3"
@pytest.mark.usefixtures("base_dir")
def test_get_release_missing_pyproject():
"""Should fall back to "NA" without a pyproject.toml."""
assert get_release() == "NA"
@pytest.mark.parametrize(
"content",
[
'[project]\nname = "meet"\n', # no version
"[tool.uv]\npackage = true\n", # no [project] table
"[project\nversion = ", # malformed TOML
],
)
def test_get_release_unreadable_version(base_dir, content):
"""Should fall back to "NA" without a readable version in pyproject.toml."""
(base_dir / "pyproject.toml").write_text(content, encoding="utf-8")
assert get_release() == "NA"
+12 -4
View File
@@ -12,7 +12,7 @@ https://docs.djangoproject.com/en/3.1/ref/settings/
# pylint: disable=too-many-lines
import tomllib
import json
import warnings
from os import path
from socket import gethostbyname, gethostname
@@ -37,11 +37,19 @@ GB = 1024 * MB
def get_release():
"""
Get the current release of the application
By release, we mean the release from the version.json file à la Mozilla [1]
(if any). If this file has not been found, it defaults to "NA".
[1]
https://github.com/mozilla-services/Dockerflow/blob/master/docs/version_object.md
"""
# Try to get the current release from the version.json file generated by the
# CI during the Docker image build
try:
with open(path.join(BASE_DIR, "pyproject.toml"), "rb") as pyproject:
return tomllib.load(pyproject)["project"]["version"]
except (FileNotFoundError, KeyError, tomllib.TOMLDecodeError):
with open(path.join(BASE_DIR, "version.json"), encoding="utf8") as version:
return json.load(version)["version"]
except FileNotFoundError:
return "NA" # Default: not available
-38
View File
@@ -1,38 +0,0 @@
# Global options
{
auto_https off
admin off
}
:{$PORT} {
root * /usr/share/nginx/html
encode gzip
# Vite fingerprints everything under /assets, so a given URL's bytes never
# change: cache it forever. A new build emits new hashed URLs.
@immutable path /assets/*
header @immutable Cache-Control "public, max-age=2592000, immutable"
# The SPA shell and other non-fingerprinted files must revalidate every
# load, or a deploy's new asset hashes only show up after a hard refresh.
@revalidate not path /assets/*
header @revalidate {
Cache-Control "no-cache, no-store, must-revalidate"
Pragma "no-cache"
Expires 0
}
route {
import /etc/caddy/media-proxy.caddy
try_files {path} /index.html
file_server
}
handle_errors {
@spa_404 expression `{err.status_code} == 404`
handle @spa_404 {
rewrite * /index.html
file_server
}
}
}
+13 -13
View File
@@ -41,24 +41,24 @@ ENV VITE_APP_TITLE=${VITE_APP_TITLE}
RUN npm run build
# ---- Caddy builder image ----
FROM caddy:2.11.4-builder AS caddy-builder
RUN xcaddy build --with github.com/caddyserver/replace-response
RUN apk add --no-cache libcap && \
setcap -r /usr/bin/caddy
# ---- Front-end image ----
FROM gcr.io/distroless/static-debian12:nonroot AS frontend-production
FROM nginxinc/nginx-unprivileged:1.30.4-alpine3.24 AS frontend-production
USER root
RUN apk del curl
USER nginx
# Un-privileged user running the application
ARG DOCKER_USER
USER ${DOCKER_USER}
COPY --from=caddy-builder /usr/bin/caddy /usr/bin/caddy
COPY --from=meet-builder \
/home/frontend/dist \
/usr/share/nginx/html
COPY ./src/frontend/Caddyfile /etc/caddy/Caddyfile
COPY ./docker/caddy/media-proxy.caddy /etc/caddy/media-proxy.caddy
COPY ./src/frontend/default.conf /etc/nginx/conf.d
COPY ./docker/files/usr/local/bin/entrypoint /usr/local/bin/entrypoint
ENV PORT=8080
ENTRYPOINT [ "/usr/local/bin/entrypoint" ]
ENTRYPOINT ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
CMD ["nginx", "-g", "daemon off;"]
+30
View File
@@ -0,0 +1,30 @@
server {
listen 8080;
server_name localhost;
server_tokens off;
root /usr/share/nginx/html;
location ^~ /assets/mediapipe/wasm/ {
expires 30d;
add_header Cache-Control "public, max-age=2592000";
}
# Serve static files with caching
location ~* ^/assets/.*\.(css|js|json|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, max-age=2592000";
}
# Serve static files
location / {
try_files $uri $uri/ /index.html;
# Add no-cache headers
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache"; # HTTP 1.0 header for backward compatibility
add_header Expires 0;
}
# Optionally, handle 404 errors by redirecting to index.html
error_page 404 =200 /index.html;
}
@@ -34,15 +34,6 @@ export const WaitingParticipantNotification = () => {
const isParticipantListEmpty = (p?: WaitingParticipant[]) => p?.length == 0
useEffect(() => {
const previousIds = new Set(prevWaitingParticipant?.map(({ id }) => id))
const hasNewWaitingParticipant = waitingParticipants.some(
({ id }) => !previousIds.has(id)
)
if (hasNewWaitingParticipant) {
triggerNotificationSound(NotificationType.ParticipantWaiting)
}
// Show notification when the first participant enters the waiting room
if (
!isParticipantListEmpty(waitingParticipants) &&
@@ -51,6 +42,8 @@ export const WaitingParticipantNotification = () => {
) {
setShowQuickActionsMessage(true)
triggerNotificationSound(NotificationType.ParticipantJoined)
if (timerRef.current !== null) {
clearTimeout(timerRef.current)
}
@@ -11,7 +11,7 @@ export const useNotificationSound = () => {
participantJoined: [0, 1150],
handRaised: [1400, 180],
messageReceived: [1580, 300],
participantWaiting: [2039, 710],
waiting: [2039, 710],
success: [2740, 1304],
},
volume: notificationsSnap.soundNotificationVolume,
@@ -102,9 +102,6 @@
},
"messageReceived": {
"label": "Nachricht erhalten"
},
"participantWaiting": {
"label": "Person im Warteraum"
}
}
},
@@ -102,9 +102,6 @@
},
"messageReceived": {
"label": "Message received"
},
"participantWaiting": {
"label": "Participant waiting"
}
}
},
@@ -102,9 +102,6 @@
},
"messageReceived": {
"label": "Un mensaje recibido"
},
"participantWaiting": {
"label": "Una persona en la sala de espera"
}
}
},
@@ -102,9 +102,6 @@
},
"messageReceived": {
"label": "Un message reçu"
},
"participantWaiting": {
"label": "Une personne en salle d’attente"
}
}
},
@@ -102,9 +102,6 @@
},
"messageReceived": {
"label": "Bericht ontvangen"
},
"participantWaiting": {
"label": "Deelnemer in de wachtkamer"
}
}
},
-1
View File
@@ -16,7 +16,6 @@ const DEFAULT_STATE: State = {
[NotificationType.ParticipantJoined, true],
[NotificationType.HandRaised, true],
[NotificationType.MessageReceived, true],
[NotificationType.ParticipantWaiting, true],
])
),
soundNotificationVolume: 0.1,
+31
View File
@@ -256,6 +256,37 @@ posthog:
ingressAssets:
enabled: false
# ---- Extra ingress/service for recording file downloads -----------
ingressMedia:
enabled: true
host: meet.127.0.0.1.nip.io
annotations:
nginx.ingress.kubernetes.io/auth-url: https://meet.127.0.0.1.nip.io/api/v1.0/recordings/media-auth/
nginx.ingress.kubernetes.io/auth-response-headers: "Authorization, X-Amz-Date, X-Amz-Content-SHA256"
nginx.ingress.kubernetes.io/upstream-vhost: minio.meet.svc.cluster.local:9000
nginx.ingress.kubernetes.io/rewrite-target: /meet-media-storage/$1
serviceMedia:
host: minio.meet.svc.cluster.local
port: 9000
# ---- Extra ingress/service for background file uploads ------------
ingressMediaFiles:
enabled: true
host: meet.127.0.0.1.nip.io
annotations:
nginx.ingress.kubernetes.io/auth-url: https://meet.127.0.0.1.nip.io/api/v1.0/files/media-auth/
nginx.ingress.kubernetes.io/auth-response-headers: "Authorization, X-Amz-Date, X-Amz-Content-SHA256"
nginx.ingress.kubernetes.io/upstream-vhost: minio.meet.svc.cluster.local:9000
nginx.ingress.kubernetes.io/rewrite-target: /meet-media-storage/files/$1
serviceMediaFiles:
host: minio.meet.svc.cluster.local
port: 9000
# ---- STT Orchestration Microservice Components --------------------
summary:
@@ -23,6 +23,9 @@ frontend:
- name: outlook-addon-manifest
configMap:
name: outlook-addon-manifest
- name: frontend-nginx-config
configMap:
name: frontend-nginx-config
extraVolumeMounts:
- name: outlook-addon-config
@@ -33,6 +36,10 @@ frontend:
mountPath: /usr/share/nginx/html/addons/outlook/manifest.xml
subPath: manifest.xml
readOnly: true
- name: frontend-nginx-config
mountPath: /etc/nginx/conf.d/default.conf
subPath: default.conf
readOnly: true
outlookAddon:
enabled: true
@@ -42,3 +49,5 @@ frontend:
appName: "Visio"
id: "a025f0f6-757a-4790-97f3-99c66c4a5795"
frontendNginxConfig:
enabled: true
@@ -55,3 +55,5 @@ agentSubtitles:
- key: cacert.pem
path: cert.pem
frontendNginxConfig:
enabled: false
@@ -0,0 +1,99 @@
{{- if .Values.frontendNginxConfig.enabled }}
apiVersion: v1
kind: ConfigMap
metadata:
name: frontend-nginx-config
namespace: {{ .Release.Namespace }}
data:
default.conf: |
server {
listen 8080;
server_name localhost;
server_tokens off;
root /usr/share/nginx/html;
location = /.well-known/windows-app-web-link {
default_type application/json;
alias /usr/share/nginx/html/.well-known/windows-app-web-link;
add_header Content-Disposition "attachment; filename=windows-app-web-link";
}
# Manifest — fetched, never iframed
location = /addons/outlook/manifest.xml {
alias /usr/share/nginx/html/addons/outlook/manifest.xml;
add_header Access-Control-Allow-Origin "*";
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header X-Frame-Options "DENY";
add_header Content-Security-Policy "frame-ancestors 'none'";
}
location = /addons/outlook/assets/ {
return 404;
}
location ~* ^/addons/outlook/assets/(.+\.(?:css|js|json|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot))/?$ {
root /usr/share/nginx/html;
expires 30d;
add_header Cache-Control "public, max-age=2592000, immutable" always;
add_header Access-Control-Allow-Origin "*";
add_header Vary "Origin" always;
}
location = /addons/outlook/ {
return 404;
}
location ~ ^/addons/outlook(/.*)?$ {
alias /usr/share/nginx/html/addons/outlook$1;
error_page 404 =200 /index.html;
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache" always;
add_header Expires 0 always;
set $ms_domains "https://*.live.com https://*.office.com https://*.microsoft.com https://*.office365.com https://*.sharepoint.com";
set $nonce $request_id;
set $csp "default-src 'self'; upgrade-insecure-requests; ";
set $csp "${csp}frame-ancestors ${ms_domains}; ";
set $csp "${csp}script-src 'nonce-${nonce}' 'strict-dynamic'; ";
set $csp "${csp}style-src 'self' 'unsafe-inline'; ";
set $csp "${csp}img-src 'self' data:; ";
set $csp "${csp}font-src 'self' data:; ";
set $csp "${csp}connect-src 'self' ${ms_domains}; ";
set $csp "${csp}frame-src 'none'; ";
set $csp "${csp}object-src 'none'; ";
set $csp "${csp}base-uri 'none'; ";
add_header Content-Security-Policy $csp;
sub_filter 'NONCE_PLACEHOLDER' $nonce;
sub_filter_once off;
}
location ^~ /assets/mediapipe/wasm/ {
expires 30d;
add_header Cache-Control "public, max-age=2592000";
}
# Serve static files with caching
location ~* ^/assets/.*\.(css|js|json|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, max-age=2592000";
}
# Serve static files
location / {
try_files $uri $uri/ /index.html;
# Add no-cache headers
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache"; # HTTP 1.0 header for backward compatibility
add_header Expires 0;
}
# Optionally, handle 404 errors by redirecting to index.html
error_page 404 =200 /index.html;
}
{{- end }}
+2 -2
View File
@@ -74,7 +74,7 @@ spec:
value: meet
- name: MINIO_ROOT_PASSWORD
value: password
image: "quay.io/minio/minio"
image: "minio/minio"
imagePullPolicy: IfNotPresent
ports:
- containerPort: 9000
@@ -103,7 +103,7 @@ spec:
spec:
containers:
- name: mc
image: quay.io/minio/mc
image: minio/mc
command:
- /bin/sh
- -c
+19 -5
View File
@@ -34,6 +34,24 @@
| `ingressAdmin.tls.secretName` | Secret name for TLS config | `nil` |
| `ingressAdmin.tls.additional[].secretName` | Secret name for additional TLS config | |
| `ingressAdmin.tls.additional[].hosts[]` | Hosts for additional TLS config | |
| `ingressMedia.enabled` | whether to enable the Ingress or not | `false` |
| `ingressMedia.className` | IngressClass to use for the Ingress | `nil` |
| `ingressMedia.host` | Host for the Ingress | `meet.example.com` |
| `ingressMedia.path` | Path to use for the Ingress | `/media/(.*)` |
| `ingressMedia.hosts` | Additional host to configure for the Ingress | `[]` |
| `ingressMedia.tls.enabled` | Weather to enable TLS for the Ingress | `true` |
| `ingressMedia.tls.secretName` | Secret name for TLS config | `nil` |
| `ingressMedia.tls.additional[].secretName` | Secret name for additional TLS config | |
| `ingressMedia.tls.additional[].hosts[]` | Hosts for additional TLS config | |
| `ingressMedia.annotations.nginx.ingress.kubernetes.io/auth-url` | | `https://meet.example.com/api/v1.0/recordings/media-auth/` |
| `ingressMedia.annotations.nginx.ingress.kubernetes.io/auth-response-headers` | | `Authorization, X-Amz-Date, X-Amz-Content-SHA256` |
| `ingressMedia.annotations.nginx.ingress.kubernetes.io/upstream-vhost` | | `minio.meet.svc.cluster.local:9000` |
| `ingressMedia.annotations.nginx.ingress.kubernetes.io/configuration-snippet` | | `add_header Content-Security-Policy "default-src 'none'" always;
` |
| `serviceMedia.host` | | `minio.meet.svc.cluster.local` |
| `serviceMedia.port` | | `9000` |
| `serviceMedia.annotations` | | `{}` |
### backend
| Name | Description | Value |
@@ -107,11 +125,7 @@
| `frontend.envVars.FROM_CONFIGMAP.configMapKeyRef.key` | Key within a ConfigMap when configuring env vars from a ConfigMap | |
| `frontend.envVars.FROM_SECRET.secretKeyRef.name` | Name of a Secret when configuring env vars from a Secret | |
| `frontend.envVars.FROM_SECRET.secretKeyRef.key` | Key within a Secret when configuring env vars from a Secret | |
| `frontend.podAnnotations` | Annotations to add to the frontend Pod | `{}` | |
| `frontend.mediaProxy.storageHost` | Hostname of the S3/MinIO endpoint serving recordings and files | `minio.meet.svc.cluster.local` |
| `frontend.mediaProxy.storageProtocol` | The protocol of the S3/MinIO endpoint serving recordings and files | http |
| `frontend.mediaProxy.storagePort` | Port of the S3/MinIO endpoint serving recordings and files | `9000` |
| `frontend.mediaProxy.bucketName` | Name of the S3/MinIO bucket storing recordings and files | `meet-media-storage` |
| `frontend.podAnnotations` | Annotations to add to the frontend Pod | `{}` |
| `frontend.service.type` | frontend Service type | `ClusterIP` |
| `frontend.service.port` | frontend Service listening port | `80` |
| `frontend.service.targetPort` | frontend container listening port | `8080` |
@@ -54,18 +54,6 @@ spec:
{{- if $envVars }}
{{- $envVars | indent 12 }}
{{- end }}
- name: BACKEND_INTERNAL_HOST
value: {{ include "meet.backend.fullname" . | quote }}
- name: BACKEND_INTERNAL_PORT
value: {{ .Values.backend.service.port | quote }}
- name: MEDIA_STORAGE_HOST
value: {{ .Values.frontend.mediaProxy.storageHost | quote }}
- name: MEDIA_STORAGE_PROTOCOL
value: {{ .Values.frontend.mediaProxy.storageProtocol | quote }}
- name: MEDIA_STORAGE_PORT
value: {{ .Values.frontend.mediaProxy.storagePort | quote }}
- name: AWS_STORAGE_BUCKET_NAME
value: {{ .Values.frontend.mediaProxy.bucketName | quote }}
{{- with .Values.frontend.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
@@ -0,0 +1,83 @@
{{- if .Values.ingressMedia.enabled -}}
{{- $fullName := include "meet.fullname" . -}}
{{- if and .Values.ingressMedia.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
{{- if not (hasKey .Values.ingressMedia.annotations "kubernetes.io/ingress.class") }}
{{- $_ := set .Values.ingressMedia.annotations "kubernetes.io/ingress.class" .Values.ingressMedia.className}}
{{- end }}
{{- end }}
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1beta1
{{- else -}}
apiVersion: extensions/v1beta1
{{- end }}
kind: Ingress
metadata:
name: {{ $fullName }}-media
namespace: {{ .Release.Namespace | quote }}
labels:
{{- include "meet.labels" . | nindent 4 }}
{{- with .Values.ingressMedia.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if and .Values.ingressMedia.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
ingressClassName: {{ .Values.ingressMedia.className }}
{{- end }}
{{- if .Values.ingressMedia.tls.enabled }}
tls:
{{- if .Values.ingressMedia.host }}
- secretName: {{ .Values.ingressMedia.tls.secretName | default (printf "%s-tls" $fullName) | quote }}
hosts:
- {{ .Values.ingressMedia.host | quote }}
{{- end }}
{{- range .Values.ingressMedia.tls.additional }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- if .Values.ingressMedia.host }}
- host: {{ .Values.ingressMedia.host | quote }}
http:
paths:
- path: {{ .Values.ingressMedia.path | quote }}
{{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }}
pathType: ImplementationSpecific
{{- end }}
backend:
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
service:
name: {{ $fullName }}-media
port:
number: {{ .Values.serviceMedia.port }}
{{- else }}
serviceName: {{ $fullName }}-media
servicePort: {{ .Values.serviceMedia.port }}
{{- end }}
{{- end }}
{{- range .Values.ingressMedia.hosts }}
- host: {{ . | quote }}
http:
paths:
- path: {{ $.Values.ingressMedia.path | quote }}
{{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }}
pathType: ImplementationSpecific
{{- end }}
backend:
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
service:
name: {{ $fullName }}-media
port:
number: {{ .Values.serviceMedia.port }}
{{- else }}
serviceName: {{ $fullName }}-media
servicePort: {{ .Values.serviceMedia.port }}
{{- end }}
{{- end }}
{{- end }}
@@ -0,0 +1,83 @@
{{- if .Values.ingressMediaFiles.enabled -}}
{{- $fullName := include "meet.fullname" . -}}
{{- if and .Values.ingressMediaFiles.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
{{- if not (hasKey .Values.ingressMediaFiles.annotations "kubernetes.io/ingress.class") }}
{{- $_ := set .Values.ingressMediaFiles.annotations "kubernetes.io/ingress.class" .Values.ingressMediaFiles.className }}
{{- end }}
{{- end }}
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1beta1
{{- else -}}
apiVersion: extensions/v1beta1
{{- end }}
kind: Ingress
metadata:
name: {{ $fullName }}-media-files
namespace: {{ .Release.Namespace | quote }}
labels:
{{- include "meet.labels" . | nindent 4 }}
{{- with .Values.ingressMediaFiles.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if and .Values.ingressMediaFiles.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
ingressClassName: {{ .Values.ingressMediaFiles.className }}
{{- end }}
{{- if .Values.ingressMediaFiles.tls.enabled }}
tls:
{{- if .Values.ingressMediaFiles.host }}
- secretName: {{ .Values.ingressMediaFiles.tls.secretName | default (printf "%s-tls" $fullName) | quote }}
hosts:
- {{ .Values.ingressMediaFiles.host | quote }}
{{- end }}
{{- range .Values.ingressMediaFiles.tls.additional }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- if .Values.ingressMediaFiles.host }}
- host: {{ .Values.ingressMediaFiles.host | quote }}
http:
paths:
- path: {{ .Values.ingressMediaFiles.path | quote }}
{{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }}
pathType: ImplementationSpecific
{{- end }}
backend:
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
service:
name: {{ $fullName }}-media-files
port:
number: {{ .Values.serviceMediaFiles.port }}
{{- else }}
serviceName: {{ $fullName }}-media-files
servicePort: {{ .Values.serviceMediaFiles.port }}
{{- end }}
{{- end }}
{{- range .Values.ingressMediaFiles.hosts }}
- host: {{ . | quote }}
http:
paths:
- path: {{ $.Values.ingressMediaFiles.path | quote }}
{{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }}
pathType: ImplementationSpecific
{{- end }}
backend:
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
service:
name: {{ $fullName }}-media-files
port:
number: {{ .Values.serviceMediaFiles.port }}
{{- else }}
serviceName: {{ $fullName }}-media-files
servicePort: {{ .Values.serviceMediaFiles.port }}
{{- end }}
{{- end }}
{{- end }}
@@ -0,0 +1,14 @@
{{- $fullName := include "meet.fullname" . -}}
{{- $component := "media-files" -}}
apiVersion: v1
kind: Service
metadata:
name: {{ $fullName }}-media-files
namespace: {{ .Release.Namespace | quote }}
labels:
{{- include "meet.common.labels" (list . $component) | nindent 4 }}
annotations:
{{- toYaml $.Values.serviceMediaFiles.annotations | nindent 4 }}
spec:
type: ExternalName
externalName: {{ $.Values.serviceMediaFiles.host }}
+14
View File
@@ -0,0 +1,14 @@
{{- $fullName := include "meet.fullname" . -}}
{{- $component := "media" -}}
apiVersion: v1
kind: Service
metadata:
name: {{ $fullName }}-media
namespace: {{ .Release.Namespace | quote }}
labels:
{{- include "meet.common.labels" (list . $component) | nindent 4 }}
annotations:
{{- toYaml $.Values.serviceMedia.annotations | nindent 4 }}
spec:
type: ExternalName
externalName: {{ $.Values.serviceMedia.host }}
+88 -10
View File
@@ -98,6 +98,94 @@ ingressAdmin:
enabled: true
additional: []
## @param ingressMedia.enabled whether to enable the Ingress or not
## @param ingressMedia.className IngressClass to use for the Ingress
## @param ingressMedia.host Host for the Ingress
## @param ingressMedia.path Path to use for the Ingress
ingressMedia:
enabled: false
className: null
host: meet.example.com
path: /media/(.*)
## @param ingressMedia.hosts Additional host to configure for the Ingress
hosts: [ ]
# - chart-example.local
## @param ingressMedia.tls.enabled Whether to enable TLS for the Ingress
## @param ingressMedia.tls.secretName Secret name for TLS config
## @skip ingressMedia.tls.additional
## @extra ingressMedia.tls.additional[].secretName Secret name for additional TLS config
## @extra ingressMedia.tls.additional[].hosts[] Hosts for additional TLS config
tls:
secretName: null
enabled: true
additional: []
## @param ingressMedia.annotations.nginx.ingress.kubernetes.io/auth-url
## @param ingressMedia.annotations.nginx.ingress.kubernetes.io/auth-response-headers
## @param ingressMedia.annotations.nginx.ingress.kubernetes.io/upstream-vhost
## @param ingressMedia.annotations.nginx.ingress.kubernetes.io/configuration-snippet
annotations:
nginx.ingress.kubernetes.io/use-regex: "true"
nginx.ingress.kubernetes.io/auth-url: https://meet.example.com/api/v1.0/recordings/media-auth/
nginx.ingress.kubernetes.io/auth-response-headers: "Authorization, X-Amz-Date, X-Amz-Content-SHA256"
nginx.ingress.kubernetes.io/upstream-vhost: minio.meet.svc.cluster.local:9000
nginx.ingress.kubernetes.io/configuration-snippet: |
add_header Content-Security-Policy "default-src 'none'" always;
## @param serviceMedia.host
## @param serviceMedia.port
## @param serviceMedia.annotations
serviceMedia:
host: minio.meet.svc.cluster.local
port: 9000
annotations: {}
## @param ingressMediaFiles.enabled whether to enable the Ingress or not
## @param ingressMediaFiles.className IngressClass to use for the Ingress
## @param ingressMediaFiles.host Host for the Ingress
## @param ingressMediaFiles.path Path to use for the Ingress
ingressMediaFiles:
enabled: false
className: null
host: meet.example.com
path: /media/files/(.*)
## @param ingressMediaFiles.hosts Additional host to configure for the Ingress
hosts: [ ]
# - chart-example.local
## @param ingressMediaFiles.tls.enabled Weather to enable TLS for the Ingress
## @param ingressMediaFiles.tls.secretName Secret name for TLS config
## @skip ingressMediaFiles.tls.additional
## @extra ingressMediaFiles.tls.additional[].secretName Secret name for additional TLS config
## @extra ingressMediaFiles.tls.additional[].hosts[] Hosts for additional TLS config
tls:
secretName: null
enabled: true
additional: []
## @param ingressMediaFiles.annotations.nginx.ingress.kubernetes.io/auth-url
## @param ingressMediaFiles.annotations.nginx.ingress.kubernetes.io/auth-response-headers
## @param ingressMediaFiles.annotations.nginx.ingress.kubernetes.io/upstream-vhost
## @param ingressMediaFiles.annotations.nginx.ingress.kubernetes.io/configuration-snippet
annotations:
nginx.ingress.kubernetes.io/use-regex: "true"
nginx.ingress.kubernetes.io/auth-url: https://meet.example.com/api/v1.0/files/media-auth/
nginx.ingress.kubernetes.io/auth-response-headers: "Authorization, X-Amz-Date, X-Amz-Content-SHA256"
nginx.ingress.kubernetes.io/upstream-vhost: minio.meet.svc.cluster.local:9000
nginx.ingress.kubernetes.io/configuration-snippet: |
add_header Content-Security-Policy "default-src 'none'" always;
add_header Content-Disposition "attachment";
## @param serviceMediaFiles.host
## @param serviceMediaFiles.port
## @param serviceMediaFiles.annotations
serviceMediaFiles:
host: minio.meet.svc.cluster.local
port: 9000
annotations: {}
## @section backend
backend:
@@ -329,16 +417,6 @@ frontend:
## @param frontend.podAnnotations Annotations to add to the frontend Pod
podAnnotations: {}
## @param frontend.mediaProxy.storageHost Hostname of the S3/MinIO endpoint serving recordings and files
## @param frontend.mediaProxy.storageProtocol Protocol of the S3/MinIO endpoint serving recordings and files
## @param frontend.mediaProxy.storagePort Port of the S3/MinIO endpoint serving recordings and files
## @param frontend.mediaProxy.bucketName Name of the S3/MinIO bucket storing recordings and files
mediaProxy:
storageHost: minio.meet.svc.cluster.local
storageProtocol: http
storagePort: 9000
bucketName: meet-media-storage
## @param frontend.service.type frontend Service type
## @param frontend.service.port frontend Service listening port
## @param frontend.service.targetPort frontend container listening port