From 865311bf49bc0074e9eb6df2f570b5c92e53f72a Mon Sep 17 00:00:00 2001 From: Dominik Polakovics Date: Fri, 14 Nov 2025 09:30:19 +0100 Subject: [PATCH] feat: initial amzebs config --- hosts/amzebs-01/configuration.nix | 74 ++++ hosts/amzebs-01/hardware-configuration.nix | 27 ++ hosts/amzebs-01/modules/mysql.nix | 29 ++ hosts/amzebs-01/modules/web/stack.nix | 321 ++++++++++++++++++ hosts/amzebs-01/secrets.yaml | 18 + hosts/amzebs-01/sites/api.ebs.amz.at.nix | 37 ++ hosts/amzebs-01/sites/api.ebs.cloonar.dev.nix | 37 ++ .../amzebs-01/sites/api.stage.ebs.amz.at.nix | 37 ++ hosts/amzebs-01/sites/default.nix | 13 + hosts/amzebs-01/sites/ebs.amz.at.nix | 49 +++ hosts/amzebs-01/sites/ebs.cloonar.dev.nix | 49 +++ hosts/amzebs-01/sites/stage.ebs.amz.at.nix | 49 +++ hosts/amzebs-01/utils | 1 + 13 files changed, 741 insertions(+) create mode 100644 hosts/amzebs-01/configuration.nix create mode 100644 hosts/amzebs-01/hardware-configuration.nix create mode 100644 hosts/amzebs-01/modules/mysql.nix create mode 100644 hosts/amzebs-01/modules/web/stack.nix create mode 100644 hosts/amzebs-01/secrets.yaml create mode 100644 hosts/amzebs-01/sites/api.ebs.amz.at.nix create mode 100644 hosts/amzebs-01/sites/api.ebs.cloonar.dev.nix create mode 100644 hosts/amzebs-01/sites/api.stage.ebs.amz.at.nix create mode 100644 hosts/amzebs-01/sites/default.nix create mode 100644 hosts/amzebs-01/sites/ebs.amz.at.nix create mode 100644 hosts/amzebs-01/sites/ebs.cloonar.dev.nix create mode 100644 hosts/amzebs-01/sites/stage.ebs.amz.at.nix create mode 120000 hosts/amzebs-01/utils diff --git a/hosts/amzebs-01/configuration.nix b/hosts/amzebs-01/configuration.nix new file mode 100644 index 0000000..4e467e1 --- /dev/null +++ b/hosts/amzebs-01/configuration.nix @@ -0,0 +1,74 @@ +{ config, lib, pkgs, ... }: { + imports = [ + ./utils/bento.nix + ./utils/modules/sops.nix + ./utils/modules/nginx.nix + + ./modules/mysql.nix + ./modules/web/stack.nix + + ./utils/modules/autoupgrade.nix + ./utils/modules/promtail + ./utils/modules/borgbackup.nix + + ./hardware-configuration.nix + + ./sites + ]; + + environment.systemPackages = with pkgs; [ + vim + screen + php82 + ]; + + time.timeZone = "Europe/Vienna"; + + sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ]; + sops.defaultSopsFile = ./secrets.yaml; + + nix.gc = { + automatic = true; + options = "--delete-older-than 60d"; + }; + + boot.tmp.cleanOnBoot = true; + zramSwap.enable = true; + + networking.hostName = "amzebs-01"; + networking.domain = "cloonar.com"; + + services.openssh.enable = true; + users.users.root.openssh.authorizedKeys.keys = [ + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDN/2SAFm50kraB1fepAizox/QRXxB7WbqVbH+5OPalDT47VIJGNKOKhixQoqhABHxEoLxdf/C83wxlCVlPV9poLfDgVkA3Lyt5r3tSFQ6QjjOJAgchWamMsxxyGBedhKvhiEzcr/Lxytnoz3kjDG8fqQJwEpdqMmJoMUfyL2Rqp16u+FQ7d5aJtwO8EUqovhMaNO7rggjPpV/uMOg+tBxxmscliN7DLuP4EMTA/FwXVzcFNbOx3K9BdpMRAaSJt4SWcJO2cS2KHA5n/H+PQI7nz5KN3Yr/upJN5fROhi/SHvK39QOx12Pv7FCuWlc+oR68vLaoCKYhnkl3DnCfc7A7" + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFshMhXwS0FQFPlITipshvNKrV8sA52ZFlnaoHd1thKg" + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIRQuPqH5fdX3KEw7DXzWEdO3AlUn1oSmtJtHB71ICoH Generated By Termius" + ]; + + programs.ssh = { + knownHosts = { + "git.cloonar.com" = { + publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDlUj7eEfS/4+z/3IhFhOTXAfpGEpNv6UWuYSL5OAhus"; + }; + }; + }; + + # backups - adjust repo for this host + borgbackup.repo = "u149513-sub10@u149513-sub10.your-backup.de:borg"; + + # Use HTTP-01 challenge for Let's Encrypt (not DNS) + security.acme.acceptTerms = true; + security.acme.defaults.email = "admin+acme@cloonar.com"; + + networking.firewall = { + enable = true; + allowedTCPPorts = [ 22 80 443 ]; + + # Allow MariaDB access only from specific IP + extraCommands = '' + iptables -A nixos-fw -p tcp --dport 3306 -s 77.119.230.30 -j nixos-fw-accept + ''; + }; + + system.stateVersion = "23.11"; +} diff --git a/hosts/amzebs-01/hardware-configuration.nix b/hosts/amzebs-01/hardware-configuration.nix new file mode 100644 index 0000000..eab29d8 --- /dev/null +++ b/hosts/amzebs-01/hardware-configuration.nix @@ -0,0 +1,27 @@ +# Hardware configuration for amzebs-01 +# This is a template - update with actual hardware configuration after installation +{ modulesPath, ... }: +{ + imports = [ (modulesPath + "/profiles/qemu-guest.nix") ]; + + boot.loader.grub = { + efiSupport = true; + efiInstallAsRemovable = true; + device = "nodev"; + configurationLimit = 2; + }; + + # Update these with actual device UUIDs and paths after installation + fileSystems."/boot" = { + device = "/dev/disk/by-uuid/CHANGEME"; + fsType = "vfat"; + }; + + fileSystems."/" = { + device = "/dev/sda1"; + fsType = "ext4"; + }; + + boot.initrd.availableKernelModules = [ "ata_piix" "uhci_hcd" "xen_blkfront" ]; + boot.initrd.kernelModules = [ "nvme" ]; +} diff --git a/hosts/amzebs-01/modules/mysql.nix b/hosts/amzebs-01/modules/mysql.nix new file mode 100644 index 0000000..f506bf7 --- /dev/null +++ b/hosts/amzebs-01/modules/mysql.nix @@ -0,0 +1,29 @@ +{ pkgs, config, ... }: +{ + + services.mysql = { + enable = true; + package = pkgs.mariadb; + settings = { + mysqld = { + max_allowed_packet = "64M"; + transaction_isolation = "READ-COMMITTED"; + binlog_format = "ROW"; + # Allow remote connections + bind-address = "0.0.0.0"; + }; + }; + + # Create read-only user for remote access on initial MySQL setup + initialScript = pkgs.writeShellScript "mysql-init.sql" '' + PASSWORD=$(cat ${config.sops.secrets.mysql-readonly-password.path}) + ${pkgs.mariadb}/bin/mysql -u root < { + inherit lib config; + })); + default = {}; + example = literalExpression '' + { + "/" = { + proxyPass = "http://localhost:3000"; + }; + }; + ''; + description = lib.mdDoc "Declarative location config"; + }; + + }; + }; +in + +{ + options.services.webstack = { + dataDir = mkOption { + type = types.path; + default = "/var/www"; + description = lib.mdDoc '' + The data directory for MySQL. + + ::: {.note} + If left as the default value of `/var/www` this directory will automatically be created before the web + server starts, otherwise you are responsible for ensuring the directory exists with appropriate ownership and permissions. + ::: + ''; + }; + + instances = mkOption { + type = types.attrsOf (types.submodule instanceOpts); + default = {}; + description = lib.mdDoc "Create vhosts for typo3"; + example = literalExpression '' + { + "typo3.example.com" = { + domain = "example.com"; + domainAliases = [ "www.example.com" ]; + phpPackage = pkgs.php81; + authorizedKeys = [ + "ssh-rsa AZA==" + ]; + }; + }; + ''; + }; + }; + + config = { + systemd.services = mapAttrs' (instance: instanceOpts: + let + domain = if instanceOpts.domain != null then instanceOpts.domain else instance; + in + nameValuePair "phpfpm-${domain}" { + serviceConfig = { + ProtectHome = lib.mkForce "tmpfs"; + BindPaths = "BindPaths=/var/www/${domain}:/var/www/${domain}"; + }; + } + ) (lib.filterAttrs (name: opts: opts.enablePhp) cfg.instances); + + services.phpfpm.pools = mapAttrs' (instance: instanceOpts: + let + domain = if instanceOpts.domain != null then instanceOpts.domain else instance; + user = if instanceOpts.user != null + then instanceOps.user + else builtins.replaceStrings ["." "-"] ["_" "_"] domain; + in + nameValuePair domain { + user = user; + settings = { + "listen.owner" = config.services.nginx.user; + "pm" = "dynamic"; + "pm.max_children" = 32; + "pm.max_requests" = 500; + "pm.start_servers" = 2; + "pm.min_spare_servers" = 2; + "pm.max_spare_servers" = 5; + "php_admin_value[error_log]" = "syslog"; + "php_admin_value[max_execution_time]" = 240; + "php_admin_value[max_input_vars]" = 1500; + "access.log" = "/var/log/$pool.access.log"; + }; + phpOptions = instanceOpts.phpOptions; + phpPackage = instanceOpts.phpPackage; + phpEnv."PATH" = pkgs.lib.makeBinPath [ instanceOpts.phpPackage ]; + } + ) (lib.filterAttrs (name: opts: opts.enablePhp) cfg.instances); + + }; + + + config.services.nginx.virtualHosts = mapAttrs' (instance: instanceOpts: + let + domain = if instanceOpts.domain != null then instanceOpts.domain else instance; + user = if instanceOpts.user != null + then instanceOps.user + else builtins.replaceStrings ["." "-"] ["_" "_"] domain; + in + nameValuePair domain { + forceSSL = true; + enableACME = true; + acmeRoot = null; + root = cfg.dataDir + "/" + domain + "/public"; + + locations = lib.mkMerge [ + instanceOpts.locations + (mkIf instanceOpts.enableDefaultLocations { + "/favicon.ico".extraConfig = '' + log_not_found off; + access_log off; + ''; + + # Cache.appcache, your document html and data + "~* \\.(?:manifest|appcache|html?|xml|json)$".extraConfig = '' + expires -1; + # access_log logs/static.log; # I don't usually include a static log + ''; + + "~* \\.(jpe?g|png)$".extraConfig = '' + set $red Z; + + if ($http_accept ~* "webp") { + set $red A; + } + + if (-f $document_root/webp/$request_uri.webp) { + set $red "''${red}B"; + } + + if ($red = "AB") { + add_header Vary Accept; + rewrite ^ /webp/$request_uri.webp; + } + ''; + + # Cache Media: images, icons, video, audio, HTC + "~* \\.(?:css|js|jpg|jpeg|gif|png|webp|avif|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc|woff2)$".extraConfig = '' + expires 1y; + access_log off; + add_header Cache-Control "public"; + ''; + + # Feed + "~* \\.(?:rss|atom)$".extraConfig = '' + expires 1h; + add_header Cache-Control "public"; + ''; + + "/".extraConfig = '' + index index.php index.html; + try_files $uri $uri/ /index.php$is_args$args; + ''; + }) + (mkIf instanceOpts.enablePhp { + "~ [^/]\\.php(/|$)".extraConfig = '' + fastcgi_split_path_info ^(.+?\.php)(/.*)$; + if (!-f $document_root$fastcgi_script_name) { + return 404; + } + include ${pkgs.nginx}/conf/fastcgi_params; + include ${pkgs.nginx}/conf/fastcgi.conf; + fastcgi_buffer_size 32k; + fastcgi_buffers 8 16k; + fastcgi_connect_timeout 240s; + fastcgi_read_timeout 240s; + fastcgi_send_timeout 240s; + fastcgi_pass unix:${config.services.phpfpm.pools."${domain}".socket}; + fastcgi_index index.php; + ''; + }) + ]; + + extraConfig = instanceOpts.extraConfig; + + + # locations = mapAttrs' (location: locationOpts: + # nameValuePair location locationOpts) instanceOpts.locations; + + } + ) cfg.instances; + + config.users.users = mapAttrs' (instance: instanceOpts: + let + domain = if instanceOpts.domain != null then instanceOpts.domain else instance; + user = if instanceOpts.user != null + then instanceOps.user + else builtins.replaceStrings ["." "-"] ["_" "_"] domain; + in + nameValuePair user { + isNormalUser = true; + createHome = true; + home = "/var/www/" + domain; + homeMode= "770"; + group = config.services.nginx.group; + openssh.authorizedKeys.keys = instanceOpts.authorizedKeys; + } + ) cfg.instances; +config.users.groups = mapAttrs' (instance: instanceOpts: + let + domain = if instanceOpts.domain != null then instanceOpts.domain else instance; + user = if instanceOpts.user != null + then instanceOps.user + else builtins.replaceStrings ["." "-"] ["_" "_"] domain; + in nameValuePair user {}) cfg.instances; + + config.services.mysql.ensureUsers = mapAttrsToList (instance: instanceOpts: + let + domain = if instanceOpts.domain != null then instanceOpts.domain else instance; + user = if instanceOpts.user != null + then instanceOps.user + else builtins.replaceStrings ["." "-"] ["_" "_"] domain; + in + mkIf instanceOpts.enableMysql { + name = user; + ensurePermissions = { + "${user}.*" = "ALL PRIVILEGES"; + }; + }) cfg.instances; + + config.services.mysql.ensureDatabases = mapAttrsToList (instance: instanceOpts: + let + domain = if instanceOpts.domain != null then instanceOpts.domain else instance; + user = if instanceOpts.user != null + then instanceOps.user + else builtins.replaceStrings ["." "-"] ["_" "_"] domain; + in + mkIf instanceOpts.enableMysql user + ) cfg.instances; + config.services.mysqlBackup.databases = mapAttrsToList (instance: instanceOpts: + let + domain = if instanceOpts.domain != null then instanceOpts.domain else instance; + user = if instanceOpts.user != null + then instanceOps.user + else builtins.replaceStrings ["." "-"] ["_" "_"] domain; + in + mkIf instanceOpts.enableMysql user + ) cfg.instances; +} diff --git a/hosts/amzebs-01/secrets.yaml b/hosts/amzebs-01/secrets.yaml new file mode 100644 index 0000000..df0acbe --- /dev/null +++ b/hosts/amzebs-01/secrets.yaml @@ -0,0 +1,18 @@ +# SOPS encrypted secrets for amzebs-01 +# Edit with: nix-shell -p sops --run 'sops hosts/amzebs-01/secrets.yaml' +# +# Required secrets: +# - borg-passphrase: Backup encryption passphrase +# - borg-ssh-key: SSH private key for backup server access +# - mysql-readonly-password: Password for read-only MySQL user (api_ebs_amz_at_ro) +# +# To initialize this file, first ensure the host SSH key exists, then run: +# sops hosts/amzebs-01/secrets.yaml + +# Placeholder structure (will be encrypted after initialization): +borg-passphrase: CHANGEME +borg-ssh-key: | + -----BEGIN OPENSSH PRIVATE KEY----- + CHANGEME + -----END OPENSSH PRIVATE KEY----- +mysql-readonly-password: CHANGEME diff --git a/hosts/amzebs-01/sites/api.ebs.amz.at.nix b/hosts/amzebs-01/sites/api.ebs.amz.at.nix new file mode 100644 index 0000000..a139806 --- /dev/null +++ b/hosts/amzebs-01/sites/api.ebs.amz.at.nix @@ -0,0 +1,37 @@ +{ pkgs, lib, config, ... }: +{ + services.webstack.instances."api.ebs.amz.at" = { + enableDefaultLocations = false; + enableMysql = true; + authorizedKeys = [ + # Add deployment SSH key here + ]; + extraConfig = '' + add_header X-Frame-Options "SAMEORIGIN"; + add_header X-Content-Type-Options "nosniff"; + + index index.php; + + charset utf-8; + + error_page 404 /index.php; + ''; + locations."/favicon.ico".extraConfig = '' + log_not_found off; + access_log off; + ''; + locations."/robots.txt".extraConfig = '' + access_log off; + log_not_found off; + ''; + + locations."/".extraConfig = '' + try_files $uri $uri/ /index.php$is_args$args; + ''; + phpPackage = pkgs.php82.withExtensions ({ enabled, all }: + enabled ++ [ all.imagick ]); + }; + + # Use HTTP-01 challenge for Let's Encrypt + services.nginx.virtualHosts."api.ebs.amz.at".acmeRoot = lib.mkForce "/var/lib/acme/acme-challenge"; +} diff --git a/hosts/amzebs-01/sites/api.ebs.cloonar.dev.nix b/hosts/amzebs-01/sites/api.ebs.cloonar.dev.nix new file mode 100644 index 0000000..f681a27 --- /dev/null +++ b/hosts/amzebs-01/sites/api.ebs.cloonar.dev.nix @@ -0,0 +1,37 @@ +{ pkgs, lib, config, ... }: +{ + services.webstack.instances."api.ebs.cloonar.dev" = { + enableDefaultLocations = false; + enableMysql = true; + authorizedKeys = [ + # Add deployment SSH key here + ]; + extraConfig = '' + add_header X-Frame-Options "SAMEORIGIN"; + add_header X-Content-Type-Options "nosniff"; + + index index.php; + + charset utf-8; + + error_page 404 /index.php; + ''; + locations."/favicon.ico".extraConfig = '' + log_not_found off; + access_log off; + ''; + locations."/robots.txt".extraConfig = '' + access_log off; + log_not_found off; + ''; + + locations."/".extraConfig = '' + try_files $uri $uri/ /index.php$is_args$args; + ''; + phpPackage = pkgs.php82.withExtensions ({ enabled, all }: + enabled ++ [ all.imagick ]); + }; + + # Use HTTP-01 challenge for Let's Encrypt + services.nginx.virtualHosts."api.ebs.cloonar.dev".acmeRoot = lib.mkForce "/var/lib/acme/acme-challenge"; +} diff --git a/hosts/amzebs-01/sites/api.stage.ebs.amz.at.nix b/hosts/amzebs-01/sites/api.stage.ebs.amz.at.nix new file mode 100644 index 0000000..e554528 --- /dev/null +++ b/hosts/amzebs-01/sites/api.stage.ebs.amz.at.nix @@ -0,0 +1,37 @@ +{ pkgs, lib, config, ... }: +{ + services.webstack.instances."api.stage.ebs.amz.at" = { + enableDefaultLocations = false; + enableMysql = true; + authorizedKeys = [ + # Add deployment SSH key here + ]; + extraConfig = '' + add_header X-Frame-Options "SAMEORIGIN"; + add_header X-Content-Type-Options "nosniff"; + + index index.php; + + charset utf-8; + + error_page 404 /index.php; + ''; + locations."/favicon.ico".extraConfig = '' + log_not_found off; + access_log off; + ''; + locations."/robots.txt".extraConfig = '' + access_log off; + log_not_found off; + ''; + + locations."/".extraConfig = '' + try_files $uri $uri/ /index.php$is_args$args; + ''; + phpPackage = pkgs.php82.withExtensions ({ enabled, all }: + enabled ++ [ all.imagick ]); + }; + + # Use HTTP-01 challenge for Let's Encrypt + services.nginx.virtualHosts."api.stage.ebs.amz.at".acmeRoot = lib.mkForce "/var/lib/acme/acme-challenge"; +} diff --git a/hosts/amzebs-01/sites/default.nix b/hosts/amzebs-01/sites/default.nix new file mode 100644 index 0000000..47b194f --- /dev/null +++ b/hosts/amzebs-01/sites/default.nix @@ -0,0 +1,13 @@ +{ ... }: { + imports = [ + # Enabled vhosts (cloonar.dev) + ./api.ebs.cloonar.dev.nix + ./ebs.cloonar.dev.nix + + # Disabled vhosts (amz.at) - uncomment to enable + # ./api.ebs.amz.at.nix + # ./api.stage.ebs.amz.at.nix + # ./ebs.amz.at.nix + # ./stage.ebs.amz.at.nix + ]; +} diff --git a/hosts/amzebs-01/sites/ebs.amz.at.nix b/hosts/amzebs-01/sites/ebs.amz.at.nix new file mode 100644 index 0000000..a37038c --- /dev/null +++ b/hosts/amzebs-01/sites/ebs.amz.at.nix @@ -0,0 +1,49 @@ +{ pkgs, lib, config, ... }: +let + domain = "ebs.amz.at"; + dataDir = "/var/www/${domain}"; +in { + services.nginx.virtualHosts."${domain}" = { + forceSSL = true; + enableACME = true; + # Use HTTP-01 challenge for Let's Encrypt + acmeRoot = lib.mkForce "/var/lib/acme/acme-challenge"; + root = "${dataDir}"; + + locations."/favicon.ico".extraConfig = '' + log_not_found off; + access_log off; + ''; + + # React client-side routing support + locations."/".extraConfig = '' + index index.html; + try_files $uri $uri/ /index.html; + ''; + + # Cache static assets + locations."~* \\.(js|jpg|gif|png|webp|css|woff2|svg|ico)$".extraConfig = '' + expires 365d; + add_header Pragma "public"; + add_header Cache-Control "public"; + ''; + + # Deny PHP execution + locations."~ [^/]\\.php(/|$)".extraConfig = '' + deny all; + ''; + }; + + users.users."${domain}" = { + isNormalUser = true; + createHome = true; + home = dataDir; + homeMode = "770"; + group = "nginx"; + openssh.authorizedKeys.keys = [ + # Add deployment SSH key here + ]; + }; + + users.groups.${domain} = {}; +} diff --git a/hosts/amzebs-01/sites/ebs.cloonar.dev.nix b/hosts/amzebs-01/sites/ebs.cloonar.dev.nix new file mode 100644 index 0000000..96d5975 --- /dev/null +++ b/hosts/amzebs-01/sites/ebs.cloonar.dev.nix @@ -0,0 +1,49 @@ +{ pkgs, lib, config, ... }: +let + domain = "ebs.cloonar.dev"; + dataDir = "/var/www/${domain}"; +in { + services.nginx.virtualHosts."${domain}" = { + forceSSL = true; + enableACME = true; + # Use HTTP-01 challenge for Let's Encrypt + acmeRoot = lib.mkForce "/var/lib/acme/acme-challenge"; + root = "${dataDir}"; + + locations."/favicon.ico".extraConfig = '' + log_not_found off; + access_log off; + ''; + + # React client-side routing support + locations."/".extraConfig = '' + index index.html; + try_files $uri $uri/ /index.html; + ''; + + # Cache static assets + locations."~* \\.(js|jpg|gif|png|webp|css|woff2|svg|ico)$".extraConfig = '' + expires 365d; + add_header Pragma "public"; + add_header Cache-Control "public"; + ''; + + # Deny PHP execution + locations."~ [^/]\\.php(/|$)".extraConfig = '' + deny all; + ''; + }; + + users.users."${domain}" = { + isNormalUser = true; + createHome = true; + home = dataDir; + homeMode = "770"; + group = "nginx"; + openssh.authorizedKeys.keys = [ + # Add deployment SSH key here + ]; + }; + + users.groups.${domain} = {}; +} diff --git a/hosts/amzebs-01/sites/stage.ebs.amz.at.nix b/hosts/amzebs-01/sites/stage.ebs.amz.at.nix new file mode 100644 index 0000000..930bb51 --- /dev/null +++ b/hosts/amzebs-01/sites/stage.ebs.amz.at.nix @@ -0,0 +1,49 @@ +{ pkgs, lib, config, ... }: +let + domain = "stage.ebs.amz.at"; + dataDir = "/var/www/${domain}"; +in { + services.nginx.virtualHosts."${domain}" = { + forceSSL = true; + enableACME = true; + # Use HTTP-01 challenge for Let's Encrypt + acmeRoot = lib.mkForce "/var/lib/acme/acme-challenge"; + root = "${dataDir}"; + + locations."/favicon.ico".extraConfig = '' + log_not_found off; + access_log off; + ''; + + # React client-side routing support + locations."/".extraConfig = '' + index index.html; + try_files $uri $uri/ /index.html; + ''; + + # Cache static assets + locations."~* \\.(js|jpg|gif|png|webp|css|woff2|svg|ico)$".extraConfig = '' + expires 365d; + add_header Pragma "public"; + add_header Cache-Control "public"; + ''; + + # Deny PHP execution + locations."~ [^/]\\.php(/|$)".extraConfig = '' + deny all; + ''; + }; + + users.users."${domain}" = { + isNormalUser = true; + createHome = true; + home = dataDir; + homeMode = "770"; + group = "nginx"; + openssh.authorizedKeys.keys = [ + # Add deployment SSH key here + ]; + }; + + users.groups.${domain} = {}; +} diff --git a/hosts/amzebs-01/utils b/hosts/amzebs-01/utils new file mode 120000 index 0000000..6b18391 --- /dev/null +++ b/hosts/amzebs-01/utils @@ -0,0 +1 @@ +../../utils \ No newline at end of file