From 100d09f6b790bb2aeff8a3d9831b3454a0b066dd Mon Sep 17 00:00:00 2001 From: h7x4 Date: Fri, 30 Jan 2026 02:04:19 +0900 Subject: [PATCH] temmie/userweb: get first iteration working --- hosts/temmie/services/nfs-mounts.nix | 93 +++++------- hosts/temmie/services/userweb.nix | 217 ++++++++++++++++++++++++++- 2 files changed, 248 insertions(+), 62 deletions(-) diff --git a/hosts/temmie/services/nfs-mounts.nix b/hosts/temmie/services/nfs-mounts.nix index dd3b751..35211e4 100644 --- a/hosts/temmie/services/nfs-mounts.nix +++ b/hosts/temmie/services/nfs-mounts.nix @@ -1,76 +1,57 @@ { lib, values, ... }: let # See microbel:/etc/exports - letters = [ "a" "b" "c" "d" "h" "i" "j" "k" "l" "m" "z" ]; + letters = [ "a" "b" "c" "d" "h" "i" "j" "k" "l" "m" "z" ]; in { systemd.targets."pvv-homedirs" = { description = "PVV Homedir Partitions"; }; - systemd.mounts = - (map (l: { - description = "PVV Homedir Partition ${l}"; + systemd.mounts = map (l: { + description = "PVV Homedir Partition ${l}"; - before = [ "remote-fs.target" ]; - wantedBy = [ "multi-user.target" ]; - requiredBy = [ "pvv-homedirs.target" ]; + before = [ "remote-fs.target" ]; + wantedBy = [ "multi-user.target" ]; + requiredBy = [ "pvv-homedirs.target" ]; - type = "nfs"; - what = "homepvv${l}.pvv.ntnu.no:/export/home/pvv/${l}"; - where = "/run/pvv-home-mounts/${l}"; + type = "nfs"; + what = "homepvv${l}.pvv.ntnu.no:/export/home/pvv/${l}"; + where = "/run/pvv-home-mounts/${l}"; - options = lib.concatStringsSep "," [ - "nfsvers=3" + options = lib.concatStringsSep "," [ + "nfsvers=3" - # NOTE: this is a bit unfortunate. The address above seems to resolve to IPv6 sometimes, - # and it doesn't seem possible to specify proto=tcp,tcp6, meaning we have to tell - # NFS which exact address to use here, despite it being specified in the `what` attr :\ - "proto=tcp" - "addr=${values.hosts.microbel.ipv4}" - "mountproto=tcp" - "mounthost=${values.hosts.microbel.ipv4}" - "port=2049" + # NOTE: this is a bit unfortunate. The address above seems to resolve to IPv6 sometimes, + # and it doesn't seem possible to specify proto=tcp,tcp6, meaning we have to tell + # NFS which exact address to use here, despite it being specified in the `what` attr :\ + "proto=tcp" + "addr=${values.hosts.microbel.ipv4}" + "mountproto=tcp" + "mounthost=${values.hosts.microbel.ipv4}" + "port=2049" - # NOTE: this is yet more unfortunate. When enabling locking, it will sometimes complain about connection failed. - # dmesg(1) reveals that it has something to do with registering the lockdv1 RPC service (errno: 111), not - # quite sure how to fix it. Living life on dangerous mode for now. - "nolock" + # NOTE: this is yet more unfortunate. When enabling locking, it will sometimes complain about connection failed. + # dmesg(1) reveals that it has something to do with registering the lockdv1 RPC service (errno: 111), not + # quite sure how to fix it. Living life on dangerous mode for now. + "nolock" - # Don't wait on every read/write - "async" + # Don't wait on every read/write + "async" - # Always keep mounted - "noauto" + # Always keep mounted + "noauto" - # We don't want to update access time constantly - "noatime" + # We don't want to update access time constantly + "noatime" - # No SUID/SGID, no special devices - "nosuid" - "nodev" + # No SUID/SGID, no special devices + "nosuid" + "nodev" - # TODO: are there cgi scripts that modify stuff in peoples homedirs? - # "ro" - "rw" - - # TODO: can we enable this and still run cgi stuff? - # "noexec" - ]; - }) letters) - ++ [{ - description = "PVV Merged Homedir OverlayFS"; - - after = [ "remote-fs.target" ]; - wantedBy = [ "multi-user.target" ]; - requiredBy = [ "pvv-homedirs.target" ]; - - type = "overlay"; - what = "overlay"; - where = "/run/pvv-home-mounts-merged"; - - options = lib.concatStringsSep "," [ - "lowerdir=${lib.concatMapStringsSep ":" (l: "/run/pvv-home-mounts/${l}") letters}" - ]; - }]; + # TODO: are there cgi scripts that modify stuff in peoples homedirs? + # "ro" + "rw" + ]; + }) letters; } diff --git a/hosts/temmie/services/userweb.nix b/hosts/temmie/services/userweb.nix index 3cfcebb..7e67263 100644 --- a/hosts/temmie/services/userweb.nix +++ b/hosts/temmie/services/userweb.nix @@ -1,27 +1,232 @@ -{ ... }: +{ config, lib, pkgs, ... }: +let + cfg = config.services.httpd; + + homeLetters = [ "a" "b" "c" "d" "h" "i" "j" "k" "l" "m" "z" ]; + + # https://nixos.org/manual/nixpkgs/stable/#ssec-php-user-guide-installing-with-extensions + phpEnv = pkgs.php.buildEnv { + extensions = { all, ... }: with all; [ + imagick + opcache + ]; + + extraConfig = '' + display_errors=0 + post_max_size = 40M + upload_max_filesize = 40M + extension=sysvsem.so + ''; + }; + + perlEnv = pkgs.perl.withPackages (ps: with ps; [ + TextPDF + CGI + LWP + XMLLibXML + ]); + + # https://nixos.org/manual/nixpkgs/stable/#python.buildenv-function + pythonEnv = pkgs.python3.buildEnv.override { + extraLibs = with pkgs.python3Packages; [ + matplotlib + requests + ]; + ignoreCollisions = true; + }; + + # https://nixos.org/manual/nixpkgs/stable/#sec-building-environment + fhsEnv = pkgs.buildEnv { + name = "userweb-env"; + paths = with pkgs; [ + bash + coreutils-full + + perlEnv + phpEnv + pythonEnv + + gnused + gawk + file + diffutils + gnugrep + util-linux + iproute2 + curl + less + + gnuplot + system-sendmail + ]; + + extraOutputsToInstall = [ + "man" + "doc" + ]; + }; +in { services.httpd = { enable = true; + adminAddr = "drift@pvv.ntnu.no"; - # extraModules = []; + # TODO: consider upstreaming systemd support + # TODO: mod_log_journald in v2.5 + package = pkgs.apacheHttpd.overrideAttrs (prev: { + nativeBuildInputs = prev.nativeBuildInputs ++ [ pkgs.pkg-config ]; + buildInputs = prev.buildInputs ++ [ pkgs.systemdLibs ]; + configureFlags = prev.configureFlags ++ [ "--enable-systemd" ]; + }); + + enablePHP = true; + phpPackage = phpEnv; + + enablePerl = true; + + # TODO: mod_log_journald in v2.5 + extraModules = [ + "systemd" + "userdir" + # TODO: I think the compilation steps of pkgs.apacheHttpdPackages.mod_perl might have some + # incorrect or restrictive assumptions upstream, either nixpkgs or source + # { + # name = "perl"; + # path = let + # mod_perl = pkgs.apacheHttpdPackages.mod_perl.override { + # apacheHttpd = cfg.package.out; + # perl = perlEnv; + # }; + # in "${mod_perl}/modules/mod_perl.so"; + # } + ]; + + extraConfig = '' + TraceEnable on + LogLevel warn rewrite:trace3 + ScriptLog ${cfg.logDir}/cgi.log + ''; # virtualHosts."userweb.pvv.ntnu.no" = { virtualHosts."temmie.pvv.ntnu.no" = { - forceSSL = true; enableACME = true; + + extraConfig = '' + UserDir ${lib.concatMapStringsSep " " (l: "/home/pvv/${l}/*/web-docs") homeLetters} + UserDir disabled root + AddHandler cgi-script .cgi + + + Options MultiViews Indexes SymLinksIfOwnerMatch ExecCGI IncludesNoExec + AllowOverride All + Require all granted + + ''; }; }; + networking.firewall.allowedTCPPorts = [ + 80 + 443 + ]; + + # socket activation comes in v2.5 + # systemd.sockets.httpd = { + # wantedBy = [ "sockets.target" ]; + # description = "HTTPD socket"; + # listenStreams = [ + # "0.0.0.0:80" + # "0.0.0.0:443" + # ]; + # }; + systemd.services.httpd = { after = [ "pvv-homedirs.target" ]; requires = [ "pvv-homedirs.target" ]; + environment = { + PATH = lib.mkForce "/usr/bin"; + }; + serviceConfig = { + Type = lib.mkForce "notify"; + + ExecStart = lib.mkForce "${cfg.package}/bin/httpd -D FOREGROUND -f /etc/httpd/httpd.conf -k start"; + ExecReload = lib.mkForce "${cfg.package}/bin/httpd -f /etc/httpd/httpd.conf -k graceful"; + ExecStop = lib.mkForce ""; + KillMode = "mixed"; + + ConfigurationDirectory = [ "httpd" ]; + LogsDirectory = [ "httpd" ]; + + CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ]; + LockPersonality = true; + PrivateDevices = true; + PrivateTmp = true; + # NOTE: this removes CAP_NET_BIND_SERVICE... + # PrivateUsers = true; + ProtectClock = true; + ProtectControlGroups = true; ProtectHome = "tmpfs"; - BindPaths = let - letters = [ "a" "b" "c" "d" "h" "i" "j" "k" "l" "m" "z" ]; - in map (l: "/run/pvv-home-mounts/${l}:/home/pvv/${l}") letters; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectSystem = true; + RemoveIPC = true; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_UNIX" + "AF_NETLINK" + ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SocketBindDeny = "any"; + SocketBindAllow = [ + "tcp:80" + "tcp:443" + ]; + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service" + ]; + UMask = "0077"; + + RuntimeDirectory = [ "httpd/root-mnt" ]; + RootDirectory = "/run/httpd/root-mnt"; + MountAPIVFS = true; + BindReadOnlyPaths = [ + builtins.storeDir + "/etc" + # NCSD socket + "/var/run" + "/var/lib/acme" + + "${fhsEnv}/bin:/bin" + "${fhsEnv}/sbin:/sbin" + "${fhsEnv}/lib:/lib" + "${fhsEnv}/share:/share" + ] ++ (lib.mapCartesianProduct ({ parent, child }: "${fhsEnv}${child}:${parent}${child}") { + parent = [ + "/local" + "/opt" + "/opt/local" + "/store" + "/store/gnu" + "/usr" + "/usr/local" + ]; + child = [ + "/bin" + "/sbin" + "/lib" + "/libexec" + "/include" + "/share" + ]; + }); + BindPaths = map (l: "/run/pvv-home-mounts/${l}:/home/pvv/${l}") homeLetters; }; };