{ 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 = { defaults.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; }; # wordpress = { # isSystemUser = true; # group = config.services.nginx.group; # }; }; 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; # }; # }; # "wordpress-designyourfuture" = { # user = config.users.users.wordpress.name; # group = config.services.nginx.group; # settings = { # "listen.owner" = config.services.nginx.user; # "pm" = "dynamic"; # "pm.max_children" = 32; # "pm.start_servers" = 2; # "pm.min_spare_servers" = 2; # "pm.max_spare_servers" = 4; # "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; auth_request_set $username $upstream_http_x_username; proxy_set_header X-WEBAUTH-USER $username; 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; }; "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/"; }; }; "www.musique-meyenheim.fr" = { enableACME = true; forceSSL = true; globalRedirect = "musique-meyenheim.fr"; }; # "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.settings.server.http_port; "transmission.nyanlout.re" = authReverse config.services.transmission.settings.rpc-port; "radarr.nyanlout.re" = authReverse 7878; "sonarr.nyanlout.re" = authReverse 8989; "syncthing.nyanlout.re" = authReverse 8384; "prowlarr.nyanlout.re" = authReverse 9696; "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.settings.server.HTTP_PORT; "musique.nyanlout.re" = simpleReverse config.services.navidrome.settings.Port; "photo.nyanlout.re" = recursiveUpdate (simpleReverse config.services.photoprism.port) { locations."/" = { proxyWebsockets = true; }; }; "zigbee.nyanlout.re" = recursiveUpdate (authReverse config.services.zigbee2mqtt.settings.frontend.port) { locations."/" = { proxyWebsockets = true; }; }; "apart.nyanlout.re" = recursiveUpdate (simpleReverse config.services.home-assistant.config.http.server_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 { "/" = { 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; fastcgi_param SCRIPT_FILENAME $document_root/index.php; fastcgi_intercept_errors on; fastcgi_buffers 64 4K; client_body_temp_path /mnt/webdav/tmp_upload; client_max_body_size 0; proxy_request_buffering off; ''; }; } // { 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}; ''; } ]; "designyourfuture.amandoline-creations.fr" = base { "/".alias = "/var/www/amandoline-designyourfuture/"; }; "amandoline-creations.fr" = base { "/".alias = "/var/www/amandoline-portfolio/"; }; "www.amandoline-creations.fr" = { enableACME = true; forceSSL = true; globalRedirect = "amandoline-creations.fr"; }; "challenge.amandoline-creations.fr" = base { "/".alias = "/var/www/amandoline-challenge/"; }; ${config.services.nextcloud.hostName} = { forceSSL = true; enableACME = true; }; "watcharr.nyanlout.re" = simpleReverse 3080; }; }; postgresql = { enable = true; package = pkgs.postgresql_14; settings = { full_page_writes = false; }; }; gitea = { enable = true; database = { type = "postgres"; port = 5432; passwordFile = "/var/lib/gitea/custom/conf/database_password"; }; settings = { server = { HTTP_PORT = 3001; ROOT_URL = "https://gitea.nyanlout.re/"; }; ui.DEFAULT_THEME = "arc-green"; log.LEVEL = "Warn"; service.DISABLE_REGISTRATION = true; session.COOKIE_SECURE = true; }; }; python-ci.enable = true; # mysql = { # enable = true; # package = pkgs.mariadb; # }; nextcloud = { enable = true; package = pkgs.nextcloud29; hostName = "cloud.nyanlout.re"; database.createLocally = true; https = true; maxUploadSize = "16G"; config = { dbtype = "pgsql"; adminpassFile = "$CREDENTIALS_DIRECTORY/nextcloud_admin.pass"; }; settings = { "preview_max_filesize_image" = "-1"; "preview_max_memory" = "1024"; "preview_ffmpeg_path" = "${pkgs.ffmpeg}/bin/ffmpeg"; "enabledPreviewProviders" = [ ''OC\Preview\BMP'' ''OC\Preview\GIF'' ''OC\Preview\JPEG'' ''OC\Preview\Krita'' ''OC\Preview\MarkDown'' ''OC\Preview\MP3'' ''OC\Preview\OpenDocument'' ''OC\Preview\PNG'' ''OC\Preview\TXT'' ''OC\Preview\XBitmap'' ''OC\Preview\Movie'' ]; }; autoUpdateApps.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.phpfpm-drive.serviceConfig = { ReadWritePaths = [ "/mnt/webdav" ]; }; systemd.services.nextcloud-setup.serviceConfig = { LoadCredential = "nextcloud_admin.pass:/mnt/secrets/nextcloud_admin.pass"; }; systemd.services.site-musique = let djangoEnv =(pkgs.python3.withPackages (ps: with ps; [ gunicorn django_4 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"; }