lol
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

nixos/lasuite-meet: init (#407742)

authored by

K900 and committed by
GitHub
a6900e58 b15fe57c

+810
+2
nixos/doc/manual/release-notes/rl-2511.section.md
··· 56 56 57 57 - [mautrix-discord](https://github.com/mautrix/discord), a Matrix-Discord puppeting/relay bridge. Available as [services.mautrix-discord](#opt-services.mautrix-discord.enable). 58 58 59 + - [SuiteNumérique Meet](https://github.com/suitenumerique/meet) is an open source alternative to Google Meet and Zoom powered by LiveKit: HD video calls, screen sharing, and chat features. Built with Django and React. Available as [services.lasuite-meet](#opt-services.lasuite-meet.enable). 60 + 59 61 ## Backward Incompatibilities {#sec-release-25.11-incompatibilities} 60 62 61 63 <!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
+1
nixos/modules/module-list.nix
··· 1596 1596 ./services/web-apps/komga.nix 1597 1597 ./services/web-apps/lanraragi.nix 1598 1598 ./services/web-apps/lasuite-docs.nix 1599 + ./services/web-apps/lasuite-meet.nix 1599 1600 ./services/web-apps/lemmy.nix 1600 1601 ./services/web-apps/limesurvey.nix 1601 1602 ./services/web-apps/mainsail.nix
+449
nixos/modules/services/web-apps/lasuite-meet.nix
··· 1 + { 2 + config, 3 + lib, 4 + pkgs, 5 + utils, 6 + ... 7 + }: 8 + let 9 + inherit (lib) 10 + getExe 11 + mapAttrs 12 + mkEnableOption 13 + mkIf 14 + mkPackageOption 15 + mkOption 16 + types 17 + optional 18 + optionalString 19 + ; 20 + 21 + cfg = config.services.lasuite-meet; 22 + 23 + pythonEnvironment = mapAttrs ( 24 + _: value: 25 + if value == null then 26 + "None" 27 + else if value == true then 28 + "True" 29 + else if value == false then 30 + "False" 31 + else 32 + toString value 33 + ) cfg.settings; 34 + 35 + commonServiceConfig = { 36 + RuntimeDirectory = "lasuite-meet"; 37 + StateDirectory = "lasuite-meet"; 38 + WorkingDirectory = "/var/lib/lasuite-meet"; 39 + 40 + User = "lasuite-meet"; 41 + DynamicUser = true; 42 + SupplementaryGroups = mkIf cfg.redis.createLocally [ 43 + config.services.redis.servers.lasuite-meet.group 44 + ]; 45 + # hardening 46 + AmbientCapabilities = ""; 47 + CapabilityBoundingSet = [ "" ]; 48 + DevicePolicy = "closed"; 49 + LockPersonality = true; 50 + NoNewPrivileges = true; 51 + PrivateDevices = true; 52 + PrivateTmp = true; 53 + PrivateUsers = true; 54 + ProcSubset = "pid"; 55 + ProtectClock = true; 56 + ProtectControlGroups = true; 57 + ProtectHome = true; 58 + ProtectHostname = true; 59 + ProtectKernelLogs = true; 60 + ProtectKernelModules = true; 61 + ProtectKernelTunables = true; 62 + ProtectProc = "invisible"; 63 + ProtectSystem = "strict"; 64 + RemoveIPC = true; 65 + RestrictAddressFamilies = [ 66 + "AF_INET" 67 + "AF_INET6" 68 + "AF_UNIX" 69 + ]; 70 + RestrictNamespaces = true; 71 + RestrictRealtime = true; 72 + RestrictSUIDSGID = true; 73 + SystemCallArchitectures = "native"; 74 + MemoryDenyWriteExecute = true; 75 + EnvironmentFile = optional (cfg.environmentFile != null) cfg.environmentFile; 76 + UMask = "0077"; 77 + }; 78 + in 79 + { 80 + options.services.lasuite-meet = { 81 + enable = mkEnableOption "SuiteNumérique Meet"; 82 + 83 + backendPackage = mkPackageOption pkgs "lasuite-meet" { }; 84 + 85 + frontendPackage = mkPackageOption pkgs "lasuite-meet-frontend" { }; 86 + 87 + bind = mkOption { 88 + type = types.str; 89 + default = "unix:/run/lasuite-meet/gunicorn.sock"; 90 + example = "127.0.0.1:8000"; 91 + description = '' 92 + The path, host/port or file descriptior to bind the gunicorn socket to. 93 + 94 + See <https://docs.gunicorn.org/en/stable/settings.html#bind> for possible options. 95 + ''; 96 + }; 97 + 98 + enableNginx = mkEnableOption "enable and configure Nginx for reverse proxying" // { 99 + default = true; 100 + }; 101 + 102 + secretKeyPath = mkOption { 103 + type = types.nullOr types.path; 104 + default = null; 105 + description = '' 106 + Path to the Django secret key. 107 + 108 + The key can be generated using: 109 + ``` 110 + python3 -c 'import secrets; print(secrets.token_hex())' 111 + ``` 112 + 113 + If not set, the secret key will be automatically generated. 114 + ''; 115 + }; 116 + 117 + postgresql = { 118 + createLocally = mkEnableOption "Configure local PostgreSQL database server for meet"; 119 + }; 120 + 121 + redis = { 122 + createLocally = mkEnableOption "Configure local Redis cache server for meet"; 123 + }; 124 + 125 + livekit = { 126 + enable = mkEnableOption "Configure local livekit server" // { 127 + default = true; 128 + }; 129 + 130 + openFirewall = mkEnableOption "Open firewall ports for livekit"; 131 + 132 + keyFile = mkOption { 133 + type = lib.types.path; 134 + description = '' 135 + LiveKit key file holding one or multiple application secrets. 136 + Use `livekit-server generate-keys` to generate a random key name and secret. 137 + 138 + The file should have the YAML format `<keyname>: <secret>`. 139 + Example: 140 + `lasuite-meet: f6lQGaHtM5HfgZjIcec3cOCRfiDqIine4CpZZnqdT5cE` 141 + 142 + Individual key/secret pairs need to be passed to clients to connect to this instance. 143 + ''; 144 + 145 + }; 146 + 147 + settings = mkOption { 148 + type = types.attrs; 149 + default = { }; 150 + description = '' 151 + Settings to pass to the livekit server. 152 + 153 + See `services.livekit.settings` for more details. 154 + ''; 155 + }; 156 + }; 157 + 158 + gunicorn = { 159 + extraArgs = mkOption { 160 + type = types.listOf types.str; 161 + default = [ 162 + "--name=meet" 163 + "--workers=3" 164 + ]; 165 + description = '' 166 + Extra arguments to pass to the gunicorn process. 167 + ''; 168 + }; 169 + }; 170 + 171 + celery = { 172 + extraArgs = mkOption { 173 + type = types.listOf types.str; 174 + default = [ ]; 175 + description = '' 176 + Extra arguments to pass to the celery process. 177 + ''; 178 + }; 179 + }; 180 + 181 + domain = mkOption { 182 + type = types.str; 183 + description = '' 184 + Domain name of the meet instance. 185 + ''; 186 + }; 187 + 188 + settings = mkOption { 189 + type = types.submodule { 190 + freeformType = types.attrsOf ( 191 + types.nullOr ( 192 + types.oneOf [ 193 + types.str 194 + types.bool 195 + types.path 196 + types.int 197 + ] 198 + ) 199 + ); 200 + 201 + options = { 202 + DJANGO_CONFIGURATION = mkOption { 203 + type = types.str; 204 + internal = true; 205 + default = "Production"; 206 + description = "The configuration that Django will use"; 207 + }; 208 + 209 + DJANGO_SETTINGS_MODULE = mkOption { 210 + type = types.str; 211 + internal = true; 212 + default = "meet.settings"; 213 + description = "The configuration module that Django will use"; 214 + }; 215 + 216 + DJANGO_SECRET_KEY_FILE = mkOption { 217 + type = types.path; 218 + default = 219 + if cfg.secretKeyPath == null then "/var/lib/lasuite-meet/django_secret_key" else cfg.secretKeyPath; 220 + description = "The path to the file containing Django's secret key"; 221 + }; 222 + 223 + DJANGO_DATA_DIR = mkOption { 224 + type = types.path; 225 + default = "/var/lib/lasuite-meet"; 226 + description = "Path to the data directory"; 227 + }; 228 + 229 + DJANGO_ALLOWED_HOSTS = mkOption { 230 + type = types.str; 231 + default = if cfg.enableNginx then "localhost,127.0.0.1,${cfg.domain}" else ""; 232 + defaultText = lib.literalExpression '' 233 + if cfg.enableNginx then "localhost,127.0.0.1,$${cfg.domain}" else "" 234 + ''; 235 + description = "Comma-separated list of hosts that are able to connect to the server"; 236 + }; 237 + 238 + DB_NAME = mkOption { 239 + type = types.str; 240 + default = "lasuite-meet"; 241 + description = "Name of the database"; 242 + }; 243 + 244 + DB_USER = mkOption { 245 + type = types.str; 246 + default = "lasuite-meet"; 247 + description = "User of the database"; 248 + }; 249 + 250 + DB_HOST = mkOption { 251 + type = types.nullOr types.str; 252 + default = if cfg.postgresql.createLocally then "/run/postgresql" else null; 253 + description = "Host of the database"; 254 + }; 255 + 256 + REDIS_URL = mkOption { 257 + type = types.nullOr types.str; 258 + default = 259 + if cfg.redis.createLocally then 260 + "unix://${config.services.redis.servers.lasuite-meet.unixSocket}?db=0" 261 + else 262 + null; 263 + description = "URL of the redis backend"; 264 + }; 265 + 266 + CELERY_BROKER_URL = mkOption { 267 + type = types.nullOr types.str; 268 + default = 269 + if cfg.redis.createLocally then 270 + "redis+socket://${config.services.redis.servers.lasuite-meet.unixSocket}?db=1" 271 + else 272 + null; 273 + description = "URL of the redis backend for celery"; 274 + }; 275 + 276 + LIVEKIT_API_URL = mkOption { 277 + type = types.nullOr types.str; 278 + default = if cfg.enableNginx && cfg.livekit.enable then "http://${cfg.domain}/livekit" else null; 279 + defaultText = lib.literalExpression '' 280 + if cfg.enableNginx && cfg.livekit.enable then 281 + "http://$${cfg.domain}/livekit" 282 + else 283 + null 284 + ''; 285 + description = "URL to the livekit server"; 286 + }; 287 + }; 288 + }; 289 + 290 + default = { }; 291 + example = '' 292 + { 293 + DJANGO_ALLOWED_HOSTS = "*"; 294 + } 295 + ''; 296 + description = '' 297 + Configuration options of meet. 298 + See https://github.com/suitenumerique/meet/blob/v${cfg.backendPackage.version}/docs/env.md 299 + `REDIS_URL` and `CELERY_BROKER_URL` are set if `services.lasuite-meet.redis.createLocally` is true. 300 + `DB_NAME` `DB_USER` and `DB_HOST` are set if `services.lasuite-meet.postgresql.createLocally` is true. 301 + ''; 302 + }; 303 + 304 + environmentFile = mkOption { 305 + type = types.nullOr types.path; 306 + default = null; 307 + description = '' 308 + Path to environment file. 309 + 310 + This can be useful to pass secrets to meet via tools like `agenix` or `sops`. 311 + ''; 312 + }; 313 + }; 314 + 315 + config = mkIf cfg.enable { 316 + systemd.services.lasuite-meet = { 317 + description = "Meet from SuiteNumérique"; 318 + after = 319 + [ "network.target" ] 320 + ++ (optional cfg.postgresql.createLocally "postgresql.service") 321 + ++ (optional cfg.redis.createLocally "redis-lasuite-meet.service"); 322 + 323 + wants = 324 + (optional cfg.postgresql.createLocally "postgresql.service") 325 + ++ (optional cfg.redis.createLocally "redis-lasuite-meet.service"); 326 + 327 + wantedBy = [ "multi-user.target" ]; 328 + 329 + preStart = '' 330 + ln -sfT ${cfg.backendPackage}/share/static /var/lib/lasuite-meet/static 331 + 332 + if [ ! -f .version ]; then 333 + touch .version 334 + fi 335 + 336 + if [ "${cfg.backendPackage.version}" != "$(cat .version)" ]; then 337 + ${getExe cfg.backendPackage} migrate 338 + echo -n "${cfg.backendPackage.version}" > .version 339 + fi 340 + ${optionalString (cfg.secretKeyPath == null) '' 341 + if [[ ! -f /var/lib/lasuite-meet/django_secret_key ]]; then 342 + ( 343 + umask 0377 344 + tr -dc A-Za-z0-9 < /dev/urandom | head -c64 | ${pkgs.moreutils}/bin/sponge /var/lib/lasuite-meet/django_secret_key 345 + ) 346 + fi 347 + ''} 348 + ''; 349 + 350 + environment = pythonEnvironment; 351 + 352 + serviceConfig = { 353 + ExecStart = utils.escapeSystemdExecArgs ( 354 + [ 355 + (lib.getExe' cfg.backendPackage "gunicorn") 356 + "--bind=${cfg.bind}" 357 + ] 358 + ++ cfg.gunicorn.extraArgs 359 + ++ [ "meet.wsgi:application" ] 360 + ); 361 + } // commonServiceConfig; 362 + }; 363 + 364 + systemd.services.lasuite-meet-celery = { 365 + description = "Meet Celery broker from SuiteNumérique"; 366 + after = 367 + [ "network.target" ] 368 + ++ (optional cfg.postgresql.createLocally "postgresql.service") 369 + ++ (optional cfg.redis.createLocally "redis-lasuite-meet.service"); 370 + 371 + wants = 372 + (optional cfg.postgresql.createLocally "postgresql.service") 373 + ++ (optional cfg.redis.createLocally "redis-lasuite-meet.service"); 374 + 375 + wantedBy = [ "multi-user.target" ]; 376 + 377 + environment = pythonEnvironment; 378 + 379 + serviceConfig = { 380 + ExecStart = utils.escapeSystemdExecArgs ( 381 + [ (lib.getExe' cfg.backendPackage "celery") ] 382 + ++ cfg.celery.extraArgs 383 + ++ [ 384 + "--app=meet.celery_app" 385 + "worker" 386 + ] 387 + ); 388 + } // commonServiceConfig; 389 + }; 390 + 391 + services.postgresql = mkIf cfg.postgresql.createLocally { 392 + enable = true; 393 + ensureDatabases = [ "lasuite-meet" ]; 394 + ensureUsers = [ 395 + { 396 + name = "lasuite-meet"; 397 + ensureDBOwnership = true; 398 + } 399 + ]; 400 + }; 401 + 402 + services.redis.servers.lasuite-meet = mkIf cfg.redis.createLocally { enable = true; }; 403 + 404 + services.livekit = mkIf cfg.livekit.enable { 405 + inherit (cfg.livekit) 406 + enable 407 + settings 408 + keyFile 409 + openFirewall 410 + ; 411 + }; 412 + 413 + services.nginx = mkIf cfg.enableNginx { 414 + enable = true; 415 + 416 + virtualHosts.${cfg.domain} = { 417 + root = cfg.frontendPackage; 418 + 419 + extraConfig = '' 420 + error_page 404 = /index.html; 421 + ''; 422 + 423 + locations."/api" = { 424 + proxyPass = "http://${cfg.bind}"; 425 + recommendedProxySettings = true; 426 + }; 427 + 428 + locations."/admin" = { 429 + proxyPass = "http://${cfg.bind}"; 430 + recommendedProxySettings = true; 431 + }; 432 + 433 + locations."/livekit" = mkIf cfg.livekit.enable { 434 + proxyPass = "http://localhost:${toString config.services.livekit.settings.port}"; 435 + recommendedProxySettings = true; 436 + proxyWebsockets = true; 437 + extraConfig = '' 438 + rewrite ^/livekit/(.*)$ /$1 break; 439 + ''; 440 + }; 441 + }; 442 + }; 443 + }; 444 + 445 + meta = { 446 + buildDocsInSandbox = false; 447 + maintainers = [ lib.maintainers.soyouzpanda ]; 448 + }; 449 + }
+1
nixos/tests/all-tests.nix
··· 771 771 _module.args.latestKernel = true; 772 772 }; 773 773 lasuite-docs = runTest ./web-apps/lasuite-docs.nix; 774 + lasuite-meet = runTest ./web-apps/lasuite-meet.nix; 774 775 lavalink = runTest ./lavalink.nix; 775 776 leaps = runTest ./leaps.nix; 776 777 lemmy = runTest ./lemmy.nix;
+141
nixos/tests/web-apps/lasuite-meet.nix
··· 1 + { lib, ... }: 2 + let 3 + domain = "meet.local"; 4 + oidcDomain = "127.0.0.1:8080"; 5 + in 6 + 7 + { 8 + name = "lasuite-meet"; 9 + meta.maintainers = with lib.maintainers; [ soyouzpanda ]; 10 + 11 + nodes.machine = 12 + { pkgs, ... }: 13 + { 14 + virtualisation.memorySize = 4 * 1024; 15 + 16 + networking.hosts."127.0.0.1" = [ domain ]; 17 + 18 + environment.systemPackages = with pkgs; [ jq ]; 19 + 20 + services.lasuite-meet = { 21 + enable = true; 22 + enableNginx = true; 23 + livekit = { 24 + enable = true; 25 + keyFile = pkgs.writeText "lasuite-meet-livekit-keys" '' 26 + lasuite-meet: ca50qKzxEXVIu61wHshAyJNzlWw8vlIwUuzxQbUK1rG 27 + ''; 28 + }; 29 + redis.createLocally = true; 30 + postgresql.createLocally = true; 31 + 32 + inherit domain; 33 + 34 + settings = { 35 + DJANGO_SECRET_KEY_FILE = pkgs.writeText "django-secret-file" '' 36 + 8540db59c03943d48c3ed1a0f96ce3b560e0f45274f120f7ee4dace3cc366a6b 37 + ''; 38 + 39 + OIDC_OP_JWKS_ENDPOINT = "http://${oidcDomain}/dex/keys"; 40 + OIDC_OP_AUTHORIZATION_ENDPOINT = "http://${oidcDomain}/dex/auth/mock"; 41 + OIDC_OP_TOKEN_ENDPOINT = "http://${oidcDomain}/dex/token"; 42 + OIDC_OP_USER_ENDPOINT = "http://${oidcDomain}/dex/userinfo"; 43 + OIDC_RP_CLIENT_ID = "lasuite-meet"; 44 + OIDC_RP_SIGN_ALGO = "RS256"; 45 + OIDC_RP_SCOPES = "openid email"; 46 + OIDC_RP_CLIENT_SECRET = "lasuitemeetclientsecret"; 47 + 48 + LOGIN_REDIRECT_URL = "http://${domain}"; 49 + LOGIN_REDIRECT_URL_FAILURE = "http://${domain}"; 50 + LOGOUT_REDIRECT_URL = "http://${domain}"; 51 + 52 + LIVEKIT_API_KEY = "lasuite-meet"; 53 + LIVEKIT_API_SECRET = "ca50qKzxEXVIu61wHshAyJNzlWw8vlIwUuzxQbUK1rG"; 54 + 55 + # Disable HTTPS feature in tests because we're running on a HTTP connection 56 + DJANGO_SECURE_PROXY_SSL_HEADER = ""; 57 + DJANGO_SECURE_SSL_REDIRECT = false; 58 + DJANGO_CSRF_COOKIE_SECURE = false; 59 + DJANGO_SESSION_COOKIE_SECURE = false; 60 + DJANGO_CSRF_TRUSTED_ORIGINS = "http://*"; 61 + }; 62 + }; 63 + 64 + services.dex = { 65 + enable = true; 66 + settings = { 67 + issuer = "http://${oidcDomain}/dex"; 68 + storage = { 69 + type = "postgres"; 70 + config.host = "/var/run/postgresql"; 71 + }; 72 + web.http = "127.0.0.1:8080"; 73 + oauth2.skipApprovalScreen = true; 74 + staticClients = [ 75 + { 76 + id = "lasuite-meet"; 77 + name = "Meet"; 78 + redirectURIs = [ "http://${domain}/api/v1.0/callback/" ]; 79 + secretFile = "/etc/dex/lasuite-meet"; 80 + } 81 + ]; 82 + connectors = [ 83 + { 84 + type = "mockPassword"; 85 + id = "mock"; 86 + name = "Example"; 87 + config = { 88 + username = "admin"; 89 + password = "password"; 90 + }; 91 + } 92 + ]; 93 + }; 94 + }; 95 + 96 + environment.etc."dex/lasuite-meet" = { 97 + mode = "0400"; 98 + user = "dex"; 99 + text = "lasuitemeetclientsecret"; 100 + }; 101 + 102 + services.postgresql = { 103 + enable = true; 104 + ensureDatabases = [ "dex" ]; 105 + ensureUsers = [ 106 + { 107 + name = "dex"; 108 + ensureDBOwnership = true; 109 + } 110 + ]; 111 + }; 112 + }; 113 + 114 + testScript = '' 115 + with subtest("Wait for units to start"): 116 + machine.wait_for_unit("dex.service") 117 + machine.wait_for_unit("lasuite-meet.service") 118 + machine.wait_for_unit("lasuite-meet-celery.service") 119 + 120 + with subtest("Wait for web servers to start"): 121 + machine.wait_until_succeeds("curl -fs 'http://${domain}/api/v1.0/authenticate/'", timeout=120) 122 + machine.wait_until_succeeds("curl -fs '${oidcDomain}/dex/auth/mock?client_id=lasuite-meet&response_type=code&redirect_uri=http://${domain}/api/v1.0/callback/&scope=openid'", timeout=120) 123 + 124 + with subtest("Login"): 125 + state, nonce = machine.succeed("curl -fs -c cjar 'http://${domain}/api/v1.0/authenticate/' -w '%{redirect_url}' | sed -n 's/.*state=\\(.*\\)&nonce=\\(.*\\)/\\1 \\2/p'").strip().split(' ') 126 + 127 + oidc_state = machine.succeed(f"curl -fs '${oidcDomain}/dex/auth/mock?client_id=lasuite-meet&response_type=code&redirect_uri=http://${domain}/api/v1.0/callback/&scope=openid+email&state={state}&nonce={nonce}' | sed -n 's/.*state=\\(.*\\)\">.*/\\1/p'").strip() 128 + 129 + code = machine.succeed(f"curl -fs '${oidcDomain}/dex/auth/mock/login?back=&state={oidc_state}' -d 'login=admin&password=password' -w '%{{redirect_url}}' | sed -n 's/.*code=\\(.*\\)&.*/\\1/p'").strip() 130 + print(f"Got approval code {code}") 131 + 132 + machine.succeed(f"curl -fs -c cjar -b cjar 'http://${domain}/api/v1.0/callback/?code={code}&state={state}'") 133 + 134 + with subtest("Create a room"): 135 + csrf_token = machine.succeed("grep csrftoken cjar | cut -f 7 | tr -d '\n'") 136 + 137 + room_id = machine.succeed(f"curl -fs -c cjar -b cjar 'http://${domain}/api/v1.0/rooms/' -X POST -H 'Content-Type: application/json' -H 'X-CSRFToken: {csrf_token}' -H 'Referer: http://${domain}' -d '{{\"name\": \"aaa-bbbb-ccc\"}}' | jq .id -r").strip() 138 + 139 + print(f"Created room with id {room_id}") 140 + ''; 141 + }
+51
pkgs/by-name/la/lasuite-meet-frontend/package.nix
··· 1 + { 2 + lib, 3 + fetchFromGitHub, 4 + fetchNpmDeps, 5 + buildNpmPackage, 6 + }: 7 + 8 + buildNpmPackage rec { 9 + pname = "lasuite-meet-frontend"; 10 + version = "0.1.27"; 11 + 12 + src = fetchFromGitHub { 13 + owner = "suitenumerique"; 14 + repo = "meet"; 15 + tag = "v${version}"; 16 + hash = "sha256-EMhsQPrONaQmNJ/FFoYlP5KKXT8vm7LwUHmEZd0oZeE="; 17 + }; 18 + 19 + sourceRoot = "source/src/frontend"; 20 + 21 + npmDeps = fetchNpmDeps { 22 + inherit version src; 23 + sourceRoot = "source/src/frontend"; 24 + hash = "sha256-7wXzcn6aGAkRUOCI6MU0AlPGngBWJtdbAfnZZDaMWec="; 25 + }; 26 + 27 + buildPhase = '' 28 + runHook preBuild 29 + 30 + npm run build 31 + 32 + runHook postBuild 33 + ''; 34 + 35 + installPhase = '' 36 + runHook preInstall 37 + 38 + cp -r dist $out 39 + 40 + runHook postInstall 41 + ''; 42 + 43 + meta = { 44 + description = "Open source alternative to Google Meet and Zoom powered by LiveKit: HD video calls, screen sharing, and chat features. Built with Django and React"; 45 + homepage = "https://github.com/suitenumerique/meet"; 46 + changelog = "https://github.com/suitenumerique/meet/blob/${src.tag}/CHANGELOG.md"; 47 + license = lib.licenses.mit; 48 + maintainers = with lib.maintainers; [ soyouzpanda ]; 49 + platforms = lib.platforms.all; 50 + }; 51 + }
+113
pkgs/by-name/la/lasuite-meet/package.nix
··· 1 + { 2 + lib, 3 + python3, 4 + fetchFromGitHub, 5 + nixosTests, 6 + }: 7 + let 8 + python = python3.override { 9 + self = python3; 10 + packageOverrides = (self: super: { django = super.django_5_2; }); 11 + }; 12 + in 13 + 14 + python.pkgs.buildPythonApplication rec { 15 + pname = "lasuite-meet"; 16 + version = "0.1.27"; 17 + pyproject = true; 18 + 19 + src = fetchFromGitHub { 20 + owner = "suitenumerique"; 21 + repo = "meet"; 22 + tag = "v${version}"; 23 + hash = "sha256-EMhsQPrONaQmNJ/FFoYlP5KKXT8vm7LwUHmEZd0oZeE="; 24 + }; 25 + 26 + sourceRoot = "source/src/backend"; 27 + 28 + patches = [ 29 + # Support configuration throught environment variables for SECURE_* 30 + ./secure_settings.patch 31 + ]; 32 + 33 + build-system = with python.pkgs; [ setuptools ]; 34 + 35 + dependencies = with python.pkgs; [ 36 + aiohttp 37 + boto3 38 + brevo-python 39 + brotli 40 + celery 41 + django 42 + django-configurations 43 + django-cors-headers 44 + django-countries 45 + django-extensions 46 + django-lasuite 47 + django-parler 48 + django-redis 49 + django-storages 50 + django-timezone-field 51 + djangorestframework 52 + dockerflow 53 + drf-spectacular 54 + drf-spectacular-sidecar 55 + easy-thumbnails 56 + factory-boy 57 + gunicorn 58 + jsonschema 59 + june-analytics-python 60 + livekit-api 61 + markdown 62 + mozilla-django-oidc 63 + nested-multipart-parser 64 + psycopg 65 + pyjwt 66 + pyopenssl 67 + python-frontmatter 68 + redis 69 + requests 70 + sentry-sdk 71 + whitenoise 72 + ]; 73 + 74 + pythonRelaxDeps = true; 75 + 76 + postBuild = '' 77 + export DJANGO_DATA_DIR=$(pwd)/data 78 + ${python.pythonOnBuildForHost.interpreter} manage.py collectstatic --noinput --clear 79 + ''; 80 + 81 + postInstall = 82 + let 83 + pythonPath = python.pkgs.makePythonPath dependencies; 84 + in 85 + '' 86 + mkdir -p $out/{bin,share} 87 + 88 + cp ./manage.py $out/bin/.manage.py 89 + cp -r data/static $out/share 90 + chmod +x $out/bin/.manage.py 91 + 92 + makeWrapper $out/bin/.manage.py $out/bin/meet \ 93 + --prefix PYTHONPATH : "${pythonPath}" 94 + makeWrapper ${lib.getExe python.pkgs.celery} $out/bin/celery \ 95 + --prefix PYTHONPATH : "${pythonPath}:$out/${python.sitePackages}" 96 + makeWrapper ${lib.getExe python.pkgs.gunicorn} $out/bin/gunicorn \ 97 + --prefix PYTHONPATH : "${pythonPath}:$out/${python.sitePackages}" 98 + ''; 99 + 100 + passthru.tests = { 101 + login-and-create-room = nixosTests.lasuite-meet; 102 + }; 103 + 104 + meta = { 105 + description = "Open source alternative to Google Meet and Zoom powered by LiveKit: HD video calls, screen sharing, and chat features. Built with Django and React"; 106 + homepage = "https://github.com/suitenumerique/meet"; 107 + changelog = "https://github.com/suitenumerique/meet/blob/${src.tag}/CHANGELOG.md"; 108 + license = lib.licenses.mit; 109 + maintainers = with lib.maintainers; [ soyouzpanda ]; 110 + mainProgram = "meet"; 111 + platforms = lib.platforms.all; 112 + }; 113 + }
+52
pkgs/by-name/la/lasuite-meet/secure_settings.patch
··· 1 + From d7bbf24df5eecb61caebdf55b0d26da60a9d9609 Mon Sep 17 00:00:00 2001 2 + From: soyouzpanda <soyouzpanda@soyouzpanda.fr> 3 + Date: Fri, 16 May 2025 23:41:12 +0200 4 + Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=94=A7(backend)=20allow=20SECURE=20en?= 5 + =?UTF-8?q?vironment=20variables=20to=20be=20configured?= 6 + MIME-Version: 1.0 7 + Content-Type: text/plain; charset=UTF-8 8 + Content-Transfer-Encoding: 8bit 9 + 10 + --- 11 + meet/settings.py | 19 ++++++++++++------- 12 + 1 file changed, 12 insertions(+), 7 deletions(-) 13 + 14 + diff --git a/meet/settings.py b/meet/settings.py 15 + index ebb0837..9c67986 100755 16 + --- a/meet/settings.py 17 + +++ b/meet/settings.py 18 + @@ -755,19 +755,24 @@ class Production(Base): 19 + # - Your proxy sets the X-Forwarded-Proto header and sends it to Django 20 + # 21 + # In other cases, you should comment the following line to avoid security issues. 22 + - SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") 23 + - SECURE_HSTS_SECONDS = 60 24 + - SECURE_HSTS_PRELOAD = True 25 + - SECURE_HSTS_INCLUDE_SUBDOMAINS = True 26 + - SECURE_SSL_REDIRECT = True 27 + + SECURE_PROXY_SSL_HEADER = values.TupleValue(("HTTP_X_FORWARDED_PROTO", "https"), 28 + + environ_name="SECURE_PROXY_SSL_HEADER") 29 + + SECURE_HSTS_SECONDS = values.IntegerValue( 30 + + 60, environ_name="SECURE_HSTS_SECONDS") 31 + + SECURE_HSTS_PRELOAD = values.BooleanValue( 32 + + True, environ_name="SECURE_HSTS_PRELOAD") 33 + + SECURE_HSTS_INCLUDE_SUBDOMAINS = values.BooleanValue( 34 + + True, environ_name="SECURE_HSTS_INCLUDE_SUBDOMAINS") 35 + + SECURE_SSL_REDIRECT = values.BooleanValue( 36 + + True, environ_name="SECURE_SSL_REDIRECT") 37 + SECURE_REDIRECT_EXEMPT = [ 38 + "^__lbheartbeat__", 39 + "^__heartbeat__", 40 + ] 41 + 42 + # Modern browsers require to have the `secure` attribute on cookies with `Samesite=none` 43 + - CSRF_COOKIE_SECURE = True 44 + - SESSION_COOKIE_SECURE = True 45 + + CSRF_COOKIE_SECURE = values.BooleanValue(True, environ_name="CSRF_COOKIE_SECURE") 46 + + SESSION_COOKIE_SECURE = values.BooleanValue(True, environ_name="SESSION_COOKIE_SECURE") 47 + 48 + # Privacy 49 + SECURE_REFERRER_POLICY = "same-origin" 50 + -- 51 + 2.47.2 52 +