From 15a3ca5e14d1d410438213d416ecf6d972e72d84 Mon Sep 17 00:00:00 2001 From: muon Date: Mon, 16 Dec 2024 15:47:21 +0000 Subject: [PATCH] Add grav --- hosts/muho/configuration.nix | 2 + modules/nixos/server/default.nix | 1 + modules/nixos/server/grav/01-nix.patch | 36 +++ modules/nixos/server/grav/default.nix | 24 ++ modules/nixos/server/grav/package.nix | 30 +++ modules/nixos/server/grav/service.nix | 317 +++++++++++++++++++++++++ 6 files changed, 410 insertions(+) create mode 100644 modules/nixos/server/grav/01-nix.patch create mode 100644 modules/nixos/server/grav/default.nix create mode 100644 modules/nixos/server/grav/package.nix create mode 100644 modules/nixos/server/grav/service.nix diff --git a/hosts/muho/configuration.nix b/hosts/muho/configuration.nix index fa079cc..6ad5671 100644 --- a/hosts/muho/configuration.nix +++ b/hosts/muho/configuration.nix @@ -27,6 +27,8 @@ in { mods.server.videos.enable = true; mods.server.reddit.enable = true; + mods.server.grav.enable = true; + mods.tailscale.enable = true; mods.wireguard.id = 3; diff --git a/modules/nixos/server/default.nix b/modules/nixos/server/default.nix index 9eed0de..e7c7be2 100644 --- a/modules/nixos/server/default.nix +++ b/modules/nixos/server/default.nix @@ -3,6 +3,7 @@ ./containers ./gaming ./docker + ./grav ./media.nix ./sync.nix diff --git a/modules/nixos/server/grav/01-nix.patch b/modules/nixos/server/grav/01-nix.patch new file mode 100644 index 0000000..31d38e1 --- /dev/null +++ b/modules/nixos/server/grav/01-nix.patch @@ -0,0 +1,36 @@ +diff -Nurp grav-src/system/src/Grav/Common/Cache.php grav-src-nixos/system/src/Grav/Common/Cache.php +--- grav-src/system/src/Grav/Common/Cache.php 1970-01-01 01:00:01.000000000 +0100 ++++ grav-src-nixos/system/src/Grav/Common/Cache.php 2024-11-22 15:46:47.481175690 +0100 +@@ -517,7 +517,7 @@ class Cache extends Getters + + $output[] = ''; + +- if (($remove === 'all' || $remove === 'standard') && file_exists($user_config)) { ++ if (($remove === 'all' || $remove === 'standard') && file_exists($user_config) && is_writable($user_config)) { + touch($user_config); + + $output[] = 'Touched: ' . $user_config; +@@ -544,7 +544,7 @@ class Cache extends Getters + { + $user_config = USER_DIR . 'config/system.yaml'; + +- if (file_exists($user_config)) { ++ if (file_exists($user_config) && is_writable($user_config)) { + touch($user_config); + } + +diff -Nurp grav-src/system/src/Grav/Console/Gpm/SelfupgradeCommand.php grav-src-nixos/system/src/Grav/Console/Gpm/SelfupgradeCommand.php +--- grav-src/system/src/Grav/Console/Gpm/SelfupgradeCommand.php 1970-01-01 01:00:01.000000000 +0100 ++++ grav-src-nixos/system/src/Grav/Console/Gpm/SelfupgradeCommand.php 2024-11-22 16:05:13.752536788 +0100 +@@ -94,6 +94,11 @@ class SelfupgradeCommand extends GpmComm + $input = $this->getInput(); + $io = $this->getIO(); + ++ # Adopted from Arch package. ++ $io->error('Grav cannot be upgraded this way as it has been installed with a distribution package.'); ++ $io->writeln('Use Nix to upgrade.'); ++ return 1; ++ + if (!class_exists(ZipArchive::class)) { + $io->title('GPM Self Upgrade'); + $io->error('php-zip extension needs to be enabled!'); diff --git a/modules/nixos/server/grav/default.nix b/modules/nixos/server/grav/default.nix new file mode 100644 index 0000000..8ffb937 --- /dev/null +++ b/modules/nixos/server/grav/default.nix @@ -0,0 +1,24 @@ +{ pkgs, lib, config, ... }: +let cfg = config.mods.server; +in with lib; { + imports = [ ./service.nix ]; + + options.mods.server = { + grav = { + enable = mkEnableOption { + default = false; + description = "enables grav service"; + }; + }; + }; + + config = { + services.grav = mkIf cfg.grav.enable { + enable = true; + systemSettings = { + log = { handler = "syslog"; }; + pages = { theme = "agency"; }; + }; + }; + }; +} diff --git a/modules/nixos/server/grav/package.nix b/modules/nixos/server/grav/package.nix new file mode 100644 index 0000000..c8bac36 --- /dev/null +++ b/modules/nixos/server/grav/package.nix @@ -0,0 +1,30 @@ +{ pkgs, lib, fetchzip, ... }: + +let version = "1.7.48"; +in pkgs.stdenvNoCC.mkDerivation { + pname = "grav"; + inherit version; + + src = fetchzip { + url = + "https://github.com/getgrav/grav/releases/download/${version}/grav-admin-v${version}.zip"; + hash = "sha256-e8WpdO0n3pY4Ajs1fvMkMI+CrR6uMitswvYS5rxal4g="; + }; + + patches = [ ./01-nix.patch ]; + + installPhase = '' + + runHook preInstall + mkdir -p $out/ + cp -R . $out/ + runHook postInstall + ''; + + meta = with lib; { + description = "Fast, simple, and flexible, file-based web platform"; + homepage = "https://getgrav.com"; + maintainers = with maintainers; [ rycee ]; + license = licenses.mit; + }; +} diff --git a/modules/nixos/server/grav/service.nix b/modules/nixos/server/grav/service.nix new file mode 100644 index 0000000..b348a52 --- /dev/null +++ b/modules/nixos/server/grav/service.nix @@ -0,0 +1,317 @@ +{ config, lib, pkgs, ... }: + +let + + inherit (lib) + generators mapAttrs mkDefault mkEnableOption mkIf mkPackageOption mkOption + types; + + cfg = config.services.grav; + + yamlFormat = pkgs.formats.yaml { }; + + poolName = "grav"; + + pkgs_grav = pkgs.callPackage ./package.nix { }; + + servedRoot = pkgs.runCommand "grav-served-root" { } '' + cp --reflink=auto --no-preserve=mode -r ${pkgs_grav} $out + + for p in assets images user system/config; do + rm -rf $out/$p + ln -sf /var/lib/grav/$p $out/$p + done + ''; + + systemSettingsYaml = + yamlFormat.generate "grav-settings.yaml" cfg.systemSettings; + +in { + options.services.grav = { + enable = mkEnableOption "grav"; + + root = mkOption { + type = types.path; + default = "/var/lib/grav"; + description = '' + + Root of the application. + ''; + }; + + pool = mkOption { + type = types.str; + default = "${poolName}"; + description = '' + Name of existing phpfpm pool that is used to run web-application. + If not specified a pool will be created automatically with + default values. + ''; + }; + + virtualHost = mkOption { + type = types.nullOr types.str; + default = "grav"; + description = '' + + Name of the nginx virtualhost to use and setup. If null, do not setup + any virtualhost. + ''; + }; + + phpPackage = mkPackageOption pkgs "php" { }; + + maxUploadSize = mkOption { + type = types.str; + default = "128M"; + description = '' + The upload limit for files. This changes the relevant options in + {file}`php.ini` and nginx if enabled. + ''; + }; + + systemSettings = mkOption { + type = yamlFormat.type; + default = { log = { handler = "syslog"; }; }; + description = '' + Settings written to {file}`user/config/system.yaml`. + ''; + }; + }; + + config = mkIf cfg.enable { + services.phpfpm.pools = mkIf (cfg.pool == "${poolName}") { + ${poolName} = { + user = "grav"; + group = "grav"; + + phpPackage = cfg.phpPackage.buildEnv { + extensions = { all, enabled }: + with all; [ + apcu + ctype + curl + dom + filter + gd + mbstring + opcache + openssl + session + simplexml + xml + yaml + zip + ]; + + extraConfig = generators.toKeyValue { + mkKeyValue = generators.mkKeyValueDefault { } " = "; + } { + output_buffering = "0"; + short_open_tag = "Off"; + expose_php = "Off"; + error_reporting = "E_ALL"; + display_errors = "stderr"; + "opcache.interned_strings_buffer" = "8"; + "opcache.max_accelerated_files" = "10000"; + "opcache.memory_consumption" = "128"; + "opcache.revalidate_freq" = "1"; + "opcache.fast_shutdown" = "1"; + "openssl.cafile" = "/etc/ssl/certs/ca-certificates.crt"; + catch_workers_output = "yes"; + + upload_max_filesize = cfg.maxUploadSize; + post_max_size = cfg.maxUploadSize; + memory_limit = cfg.maxUploadSize; + "apc.enable_cli" = "1"; + }; + }; + + phpEnv = { + GRAV_ROOT = toString servedRoot; + GRAV_SYSTEM_PATH = "${servedRoot}/system"; + GRAV_CACHE_PATH = "/var/cache/grav"; + GRAV_BACKUP_PATH = "/var/lib/grav/backup"; + GRAV_LOG_PATH = "/var/log/grav"; + GRAV_TMP_PATH = "/var/tmp/grav"; + }; + + settings = mapAttrs (name: mkDefault) { + "listen.owner" = config.services.nginx.user; + "listen.group" = config.services.nginx.group; + "listen.mode" = "0600"; + "pm" = "dynamic"; + "pm.max_children" = 75; + "pm.start_servers" = 10; + "pm.min_spare_servers" = 5; + "pm.max_spare_servers" = 20; + "pm.max_requests" = 500; + "catch_workers_output" = 1; + }; + }; + }; + + services.nginx = mkIf (cfg.virtualHost != null) { + enable = true; + virtualHosts = { + ${cfg.virtualHost} = { + root = "${servedRoot}"; + + locations = { + "= /robots.txt" = { + priority = 100; + extraConfig = '' + allow all; + access_log off; + ''; + }; + + "~ \\.php$" = { + priority = 200; + extraConfig = '' + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass unix:${ + config.services.phpfpm.pools.${cfg.pool}.socket + }; + fastcgi_index index.php; + ''; + }; + + "~* /(\\.git|cache|bin|logs|backup|tests)/.*$" = { + priority = 300; + extraConfig = '' + return 403; + ''; + }; + + # deny running scripts inside core system folders + "~* /(system|vendor)/.*\\.(txt|xml|md|html|htm|shtml|shtm|json|yaml|yml|php|php2|php3|php4|php5|phar|phtml|pl|py|cgi|twig|sh|bat)$" = + { + priority = 300; + extraConfig = '' + return 403; + ''; + }; + + # deny running scripts inside user folder + "~* /user/.*\\.(txt|md|json|yaml|yml|php|php2|php3|php4|php5|phar|phtml|pl|py|cgi|twig|sh|bat)$" = + { + priority = 300; + extraConfig = '' + return 403; + ''; + }; + + # deny access to specific files in the root folder + "~ /(LICENSE\\.txt|composer\\.lock|composer\\.json|nginx\\.conf|web\\.config|htaccess\\.txt|\\.htaccess)" = + { + priority = 300; + extraConfig = '' + return 403; + ''; + }; + + # deny all files and folder beginning with a dot (hidden files & folders) + "~ (^|/)\\." = { + priority = 300; + extraConfig = '' + return 403; + ''; + }; + + "/" = { + priority = 400; + index = "index.php"; + extraConfig = '' + try_files $uri $uri/ /index.php?$query_string; + ''; + }; + }; + + extraConfig = '' + index index.php index.html /index.php$request_uri; + add_header X-Content-Type-Options nosniff; + add_header X-XSS-Protection "1; mode=block"; + add_header X-Robots-Tag "noindex, nofollow"; + add_header X-Download-Options noopen; + add_header X-Permitted-Cross-Domain-Policies none; + add_header X-Frame-Options sameorigin; + add_header Referrer-Policy no-referrer; + client_max_body_size ${cfg.maxUploadSize}; + fastcgi_buffers 64 4K; + fastcgi_hide_header X-Powered-By; + gzip on; + gzip_vary on; + gzip_comp_level 4; + gzip_min_length 256; + gzip_proxied expired no-cache no-store private no_last_modified no_etag auth; + gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy; + ''; + }; + }; + }; + + systemd.tmpfiles.rules = let datadir = "/var/lib/grav"; + in map (dir: "d '${dir}' 0750 grav grav - -") [ + "/var/cache/grav" + "${datadir}/assets" + "${datadir}/backup" + "${datadir}/images" + "${datadir}/system/config" + "${datadir}/user/accounts" + "${datadir}/user/config" + "${datadir}/user/data" + "/var/log/grav" + ]; + # ++ [ + # "L+ ${datadir}/user/config/system.yaml - - - - ${systemSettingsYaml}" + # ]; + + systemd.services = { + "phpfpm-${poolName}" = mkIf (cfg.pool == "${poolName}") { + # restartTriggers = [ servedRoot systemSettingsYaml ]; + restartTriggers = [ servedRoot ]; + + serviceConfig = { + ExecStartPre = pkgs.writeShellScript "grav-pre-start" '' + function setPermits() { + chmod -R o-rx "$1" + chown -R grav:grav "$1" + } + + tmpDir=/var/tmp/grav + dataDir=/var/lib/grav + + mkdir $tmpDir + setPermits $tmpDir + + for path in config/site.yaml pages plugins themes; do + fullPath="$dataDir/user/$path" + if [[ ! -e $fullPath ]]; then + cp --reflink=auto --no-preserve=mode -r \ + ${pkgs_grav}/user/$path $fullPath + fi + setPermits $fullPath + done + + systemConfigDir=$dataDir/system/config + if [[ ! -e $systemConfigDir/system.yaml ]]; then + cp --reflink=auto --no-preserve=mode -r \ + ${pkgs_grav}/system/config/* $systemConfigDir/ + fi + setPermits $systemConfigDir + ''; + }; + }; + }; + + users.users.grav = { + isSystemUser = true; + description = "Grav service user"; + home = "/var/lib/grav"; + group = "grav"; + }; + + users.groups.grav = { members = [ config.services.nginx.user ]; }; + }; +}