{ config, lib, pkgs, ... }: with lib; let nginxSsoAuth = pkgs.writeText "nginx-sso_auth.inc" '' # Protect this location using the auth_request auth_request /sso-auth; # Redirect the user to the login page when they are not logged in error_page 401 = @error401; location /sso-auth { # Do not allow requests from outside internal; # Access /auth endpoint to query login state proxy_pass http://127.0.0.1:${toString(config.services.nginx.sso.configuration.listen.port)}/auth; # Do not forward the request body (nginx-sso does not care about it) proxy_pass_request_body off; proxy_set_header Content-Length ""; # Set custom information for ACL matching: Each one is available as # a field for matching: X-Host = x-host, ... proxy_set_header X-Origin-URI $request_uri; proxy_set_header X-Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # If the user is lead to /logout redirect them to the logout endpoint # of ngninx-sso which then will redirect the user to / on the current host location /sso-logout { return 302 https://login.nyanlout.re/logout?go=$scheme://$http_host/; } # Define where to send the user to login and specify how to get back location @error401 { return 302 https://login.nyanlout.re/login?go=$scheme://$http_host$request_uri; } ''; in { security.acme = { email = "paul@nyanlout.re"; acceptTerms = true; }; users.groups = { work = {}; webdav = {}; }; users.users = { work = { isSystemUser = true; group = config.users.groups.work.name; }; webdav = { isSystemUser = true; group = config.users.groups.webdav.name; }; }; services = { phpfpm.pools = { work = { user = config.users.users.work.name; phpPackage = pkgs.php.withExtensions ({ all, ... }: with all; [ redis filter ]); settings = { "listen.owner" = config.services.nginx.user; "pm" = "dynamic"; "pm.max_children" = 75; "pm.start_servers" = 10; "pm.min_spare_servers" = 5; "pm.max_spare_servers" = 20; "pm.max_requests" = 500; }; }; drive = { user = config.users.users.webdav.name; settings = { "listen.owner" = config.services.nginx.user; "pm" = "dynamic"; "pm.max_children" = 75; "pm.start_servers" = 10; "pm.min_spare_servers" = 5; "pm.max_spare_servers" = 20; "pm.max_requests" = 500; }; phpOptions = '' output_buffering=off ''; }; }; nginx = { enable = true; package = pkgs.nginx.override { modules = with pkgs.nginxModules; [ dav moreheaders ]; }; recommendedGzipSettings = true; recommendedOptimisation = true; recommendedProxySettings = true; recommendedTlsSettings = true; commonHttpConfig = '' map $scheme $hsts_header { https "max-age=31536000; includeSubdomains; preload"; } add_header Strict-Transport-Security $hsts_header; add_header Referrer-Policy origin-when-cross-origin; ''; sso = { enable = true; configuration = { listen = { addr = "127.0.0.1"; port = 8082; }; login = { title = "LoutreOS login"; default_method = "simple"; hide_mfa_field = true; names.simple = "Username / Password"; }; cookie = { domain = ".nyanlout.re"; secure = true; }; audit_log = { targets = [ "fd://stdout" ]; events = [ "access_denied" "login_success" "login_failure" "logout" ]; }; providers.simple = { enable_basic_auth = true; users = { paul = "$2y$10$RMqeJF/hUasXZ5/SLKAu4uKKp6ac6qXCaRu4OY/fIN6ZYucDXzqYm"; }; groups = { admins = [ "paul" ]; }; }; acl = { rule_sets = [ { rules = [ { field = "x-host"; regexp = ".*"; } ]; allow = [ "@admins" ]; } ]; }; }; }; virtualHosts = let base = locations: { locations = locations // { "@maintenance" = { root = "/var/www/errorpages/"; extraConfig = '' rewrite ^(.*)$ /50x.html break; ''; }; }; forceSSL = true; enableACME = true; extraConfig = '' error_page 500 502 503 504 = @maintenance; ''; }; simpleReverse = rport: base { "/" = { proxyPass = "http://127.0.0.1:${toString(rport)}/"; }; }; authReverse = rport: zipAttrsWith (name: vs: if name == "extraConfig" then (concatStrings vs) else elemAt vs 0) [ (base { "/" = { proxyPass = "http://127.0.0.1:${toString(rport)}/"; extraConfig = '' auth_request_set $cookie $upstream_http_set_cookie; add_header Set-Cookie $cookie; ''; }; }) { extraConfig = '' include ${nginxSsoAuth}; ''; } ]; in { "nyanlout.re" = base { "/" = { alias = "/var/www/site-perso/"; }; "/maintenance/" = { alias = "/var/www/errorpages/"; }; "/.well-known/openpgpkey/" = { alias = "/var/lib/gnupg/wks/nyanlout.re"; extraConfig = '' add_header Access-Control-Allow-Origin * always; ''; }; } // { default = true; }; "riot.nyanlout.re" = base { "/" = { root = pkgs.element-web; }; }; "factorio.nyanlout.re" = base { "/" = { root = "/var/www/factorio"; }; }; "minecraft.nyanlout.re" = base { "/" = { root = "/var/www/minecraft-overviewer"; }; }; "musique-meyenheim.fr" = base { "/" = { proxyPass = "http://unix:/run/site-musique.sock"; }; "/static/" = { alias = "/var/www/site-musique/staticfiles/"; }; "/media/" = { alias = "/var/www/site-musique/media/"; }; }; "maxspiegel.fr" = base { "/" = { root = "/run/python-ci/nyanloutre/site-max"; }; }; "stream.nyanlout.re" = base { "/" = { proxyPass = "http://10.30.135.71"; }; }; "login.nyanlout.re" = simpleReverse config.services.nginx.sso.configuration.listen.port; "grafana.nyanlout.re" = authReverse config.services.grafana.port; "transmission.nyanlout.re" = authReverse config.services.transmission.port; "radarr.nyanlout.re" = authReverse 7878; "sonarr.nyanlout.re" = authReverse 8989; "syncthing.nyanlout.re" = authReverse 8384; "jackett.nyanlout.re" = authReverse 9117; "pgmanage.nyanlout.re" = authReverse config.services.pgmanage.port; "matrix.nyanlout.re" = simpleReverse 8008; "emby.nyanlout.re" = recursiveUpdate (simpleReverse 8096) { locations."/" = { proxyWebsockets = true; }; }; "ci.nyanlout.re" = simpleReverse 52350; "gitea.nyanlout.re" = simpleReverse config.services.gitea.httpPort; "musique.nyanlout.re" = simpleReverse config.services.navidrome.settings.Port; "apart.nyanlout.re" = recursiveUpdate (simpleReverse config.services.home-assistant.port) { locations."/" = { proxyWebsockets = true; }; }; # "work.rezom.eu" = base { # "/" = { # index = "/_h5ai/public/index.php"; # extraConfig = '' # dav_ext_methods PROPFIND OPTIONS; # ''; # }; # "~ ^/(_h5ai/public/index|random).php" = { # extraConfig = '' # fastcgi_split_path_info ^(.+\.php)(/.+)$; # fastcgi_pass unix:${config.services.phpfpm.pools.work.socket}; # include ${pkgs.nginx}/conf/fastcgi_params; # include ${pkgs.nginx}/conf/fastcgi.conf; # ''; # }; # } // { # root = "/mnt/medias/iso_linux"; # extraConfig = '' # access_log /var/log/nginx/$host.log; # ''; # }; "drive.nyanlout.re" = base { "/" = { index = "/index.php"; extraConfig = '' fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass unix:${config.services.phpfpm.pools.drive.socket}; include ${pkgs.nginx}/conf/fastcgi_params; include ${pkgs.nginx}/conf/fastcgi.conf; client_max_body_size 0; ''; }; } // { root = "/mnt/webdav"; }; "rspamd.nyanlout.re" = zipAttrsWith (name: vs: if name == "extraConfig" then (concatStrings vs) else elemAt vs 0) [ (base { "/" = { proxyPass = "http://unix:/run/rspamd/worker-controller.sock"; extraConfig = '' auth_request_set $cookie $upstream_http_set_cookie; add_header Set-Cookie $cookie; ''; }; }) { extraConfig = '' include ${nginxSsoAuth}; ''; } ]; }; }; postgresql = { enable = true; settings = { full_page_writes = false; }; }; pgmanage = { enable = true; port = 10006; connections = { localhost = "hostaddr=127.0.0.1 port=5432 dbname=postgres"; }; }; gitea = { enable = true; cookieSecure = true; httpPort = 3001; rootUrl = "https://gitea.nyanlout.re/"; database = { type = "postgres"; port = 5432; passwordFile = "/var/lib/gitea/custom/conf/database_password"; }; log.level = "Warn"; disableRegistration = true; settings = { ui.DEFAULT_THEME = "arc-green"; }; }; python-ci.enable = true; }; systemd.services.nginx.serviceConfig = { ReadWritePaths = [ "/var/www/hls" "/mnt/webdav" ]; }; systemd.services.phpfpm-work.serviceConfig = { ReadOnlyPaths = "/mnt/medias/iso_linux"; ReadWritePaths = [ "/mnt/medias/iso_linux/_h5ai" ]; }; systemd.services.site-musique = let djangoEnv =(pkgs.python3.withPackages (ps: with ps; [ gunicorn django_3 pillow setuptools ])); in { description = "Site Django de la musique de Meyenheim"; after = [ "network.target" ]; requires = [ "site-musique.socket" ]; preStart = '' ${djangoEnv}/bin/python manage.py migrate; ${djangoEnv}/bin/python manage.py collectstatic --no-input; ''; environment = { DJANGO_SETTINGS_MODULE = "site_musique.settings.prod"; NGINX_DIRECTORY = "/var/www/site-musique"; }; serviceConfig = { DynamicUser = true; Group = "nginx"; StateDirectory = "site-musique"; WorkingDirectory = "/var/www/site-musique/"; ReadWritePaths = [ "/var/www/site-musique/staticfiles" "/var/www/site-musique/media" ]; EnvironmentFile = "/mnt/secrets/site-musique.env"; ExecStart = ''${djangoEnv}/bin/gunicorn \ --access-logfile - \ --bind unix:/run/site-musique.sock \ site_musique.wsgi:application ''; PrivateTmp = true; }; }; systemd.sockets.site-musique = { description = "Site Musique socket"; wantedBy = [ "sockets.target" ]; listenStreams = [ "/run/site-musique.sock" ]; }; systemd.services.nginx-sso.serviceConfig.EnvironmentFile = "/mnt/secrets/nginx-sso.env"; }