diff --git a/hosts/dark-firepit/hardware-configuration.nix b/hosts/dark-firepit/hardware-configuration.nix index d0e1cfa..37847e9 100644 --- a/hosts/dark-firepit/hardware-configuration.nix +++ b/hosts/dark-firepit/hardware-configuration.nix @@ -22,6 +22,15 @@ nix.settings.cores = 3; nix.settings.max-jobs = 6; + # disabling this is what's considered a "Bad Idea" + # however it is required by packages/ghost.nix, which + # is borrowed from https://notes.abhinavsarkar.net/2022/ghost-on-nixos + # + # i don't know of a cleaner way to do this, and i + # don't want to deal with ghost any longer than i + # already have, so This Will Do + nix.settings.sandbox = false; + modules.hardware.fs = { enable = true; ssd.enable = true; diff --git a/hosts/dark-firepit/webapps/default.nix b/hosts/dark-firepit/webapps/default.nix index 2337b3f..50b605f 100644 --- a/hosts/dark-firepit/webapps/default.nix +++ b/hosts/dark-firepit/webapps/default.nix @@ -98,6 +98,12 @@ in { domain = "dev-firepit.oat.zone"; port = 4444; }; + + ghost = { + enable = true; + domain = "blog.oat.zone"; + port = 1357; + }; }; }; diff --git a/modules/services/ghost.nix b/modules/services/ghost.nix new file mode 100644 index 0000000..8cf75a3 --- /dev/null +++ b/modules/services/ghost.nix @@ -0,0 +1,158 @@ +{ pkgs, lib, config, options, ... }: + +with lib; +let + cfg = config.modules.services.ghost; + # user used to run the Ghost service + userName = builtins.replaceStrings [ "." ] [ "_" ] cfg.domain; +in { + options.modules.services.ghost = { + enable = mkOption { + type = types.bool; + default = false; + }; + package = mkOption { + type = types.package; + default = pkgs._.ghost; + }; + domain = mkOption { + type = types.str; + default = "blog.oat.zone"; + }; + port = mkOption { + type = types.int; + default = 1357; + }; + dataDir = mkOption { + type = types.str; + default = "/var/lib/${userName}"; + }; + }; + + config = let + # directory used to save the blog content + dataDir = cfg.dataDir; + # script that sets up the Ghost content directory + setupScript = pkgs.writeScript "${cfg.domain}-setup.sh" '' + #! ${pkgs.stdenv.shell} -e + chmod g+s "${dataDir}" + [[ ! -d "${dataDir}/content" ]] && cp -r "${cfg.package}/content" "${dataDir}/content" + chown -R "${userName}":"${userName}" "${dataDir}/content" + chmod -R +w "${dataDir}/content" + ln -f -s "/etc/${cfg.domain}.json" "${dataDir}/config.production.json" + [[ -d "${dataDir}/current" ]] && rm "${dataDir}/current" + ln -f -s "${cfg.package}/current" "${dataDir}/current" + [[ -d "${dataDir}/content/themes/casper" ]] && rm "${dataDir}/content/themes/casper" + ln -f -s "${cfg.package}/current/content/themes/casper" "${dataDir}/content/themes/casper" + ''; + in lib.mkIf cfg.enable { + # Creates the user and group + users.users.${userName} = { + isSystemUser = true; + group = userName; + createHome = true; + home = dataDir; + }; + users.groups.${userName} = { }; + + # Creates the Ghost config + environment.etc."${cfg.domain}.json".text = '' + { + "url": "https://${cfg.domain}", + "server": { + "port": ${toString cfg.port}, + "host": "0.0.0.0" + }, + "database": { + "client": "mysql", + "connection": { + "host": "localhost", + "user": "${userName}", + "database": "${userName}", + "password": "", + "socketPath": "/run/mysqld/mysqld.sock" + } + }, + "mail": { + "transport": "sendmail" + }, + "logging": { + "transports": ["stdout"] + }, + "paths": { + "contentPath": "${dataDir}/content" + } + } + ''; + + # Sets up the Systemd service + systemd.services."${cfg.domain}" = { + enable = true; + description = "${cfg.domain} ghost blog"; + restartIfChanged = true; + restartTriggers = + [ cfg.package config.environment.etc."${cfg.domain}.json".source ]; + requires = [ "mysql.service" ]; + after = [ "mysql.service" ]; + path = [ pkgs.nodejs pkgs.vips ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + User = userName; + Group = userName; + WorkingDirectory = dataDir; + # Executes the setup script before start + ExecStartPre = setupScript; + # Runs Ghost with node + ExecStart = "${pkgs.nodejs}/bin/node current/index.js"; + # Sandboxes the Systemd service + AmbientCapabilities = [ ]; + CapabilityBoundingSet = [ ]; + KeyringMode = "private"; + LockPersonality = true; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateMounts = true; + PrivateTmp = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectSystem = "full"; + RemoveIPC = true; + RestrictAddressFamilies = [ ]; + RestrictNamespaces = true; + RestrictRealtime = true; + }; + environment = { NODE_ENV = "production"; }; + }; + + # Sets up the blog virtual host on NGINX + services.nginx.virtualHosts.${cfg.domain} = { + # Sets up Lets Encrypt SSL certificates for the blog + forceSSL = true; + enableACME = true; + locations."/" = { proxyPass = "http://127.0.0.1:${toString cfg.port}"; }; + extraConfig = '' + charset UTF-8; + + add_header Strict-Transport-Security "max-age=2592000; includeSubDomains" always; + add_header Referrer-Policy "strict-origin-when-cross-origin"; + add_header X-Frame-Options "SAMEORIGIN"; + add_header X-XSS-Protection "1; mode=block"; + add_header X-Content-Type-Options nosniff; + ''; + }; + + # Sets up MySQL database and user for Ghost + services.mysql = { + ensureDatabases = [ userName ]; + ensureUsers = [{ + name = userName; + ensurePermissions = { "${userName}.*" = "ALL PRIVILEGES"; }; + }]; + }; + }; +} diff --git a/packages/ghost/builder.sh b/packages/ghost/builder.sh new file mode 100644 index 0000000..f1ae5bd --- /dev/null +++ b/packages/ghost/builder.sh @@ -0,0 +1,9 @@ +source "$stdenv"/setup + +export HOME=$(mktemp -d) + +npm install --loglevel=info --logs-max=0 "ghost-cli@$ghostCliVersion" + +mkdir --parents "$out"/ +node_modules/ghost-cli/bin/ghost install "$version" --db=sqlite3 \ + --no-enable --no-prompt --no-stack --no-setup --no-start --dir "$out" diff --git a/packages/ghost/default.nix b/packages/ghost/default.nix new file mode 100644 index 0000000..9742ae1 --- /dev/null +++ b/packages/ghost/default.nix @@ -0,0 +1,11 @@ +{ pkgs }: + +let + pname = "ghost"; + version = "5.33.2"; +in pkgs.stdenv.mkDerivation { + inherit pname version; + buildInputs = with pkgs; [ nodejs yarn vips ]; + ghostCliVersion = "1.24.0"; + builder = ./builder.sh; +}