summaryrefslogtreecommitdiff
path: root/modules/foundation/www
diff options
context:
space:
mode:
authorMel <mel@rnrd.eu>2025-04-16 12:49:53 +0200
committerMel <mel@rnrd.eu>2025-04-16 12:49:53 +0200
commit5e1a9f0179c0beb73b63140a3ee20c4acb04fcc4 (patch)
treee2d3c83e761dc2bec056d81c268bc500ad148a6c /modules/foundation/www
parent6c9f992808b6cf8b079f4c8cfa5625de1e624618 (diff)
downloadnetwork-5e1a9f0179c0beb73b63140a3ee20c4acb04fcc4.tar.zst
network-5e1a9f0179c0beb73b63140a3ee20c4acb04fcc4.zip
Move WWW configuration into foundation module, and make it configurable
Signed-off-by: Mel <mel@rnrd.eu>
Diffstat (limited to 'modules/foundation/www')
-rw-r--r--modules/foundation/www/default.nix130
-rw-r--r--modules/foundation/www/tailnet.nix121
2 files changed, 251 insertions, 0 deletions
diff --git a/modules/foundation/www/default.nix b/modules/foundation/www/default.nix
new file mode 100644
index 0000000..2e2b662
--- /dev/null
+++ b/modules/foundation/www/default.nix
@@ -0,0 +1,130 @@
+{
+ me,
+ config,
+ pkgs,
+ lib,
+ util,
+ ...
+}:
+
+let
+ inherit (lib)
+ mergeAttrsList
+ mkIf
+ mkEnableOption
+ mkOption
+ ;
+ inherit (config.age) secrets;
+
+ cfg = config.foundation.www;
+
+ rnrdUrl = if me.is.renard then "rnrd.eu" else "${me.name}.rnrd.eu";
+
+ default-page-index = pkgs.substituteAll {
+ src = ../../../assets/base.html;
+ env.me = util.titleCase me.name;
+ };
+
+ default-page = pkgs.linkFarm "www-base" {
+ "index.html" = default-page-index;
+ "favicon.png" = ../../../assets/favicon.png;
+ };
+
+ certificate = domain: {
+ ${domain} = {
+ domain = "*.${domain}";
+ extraDomainNames = [ domain ];
+
+ dnsProvider = "cloudflare";
+ credentialFiles = {
+ CLOUDFLARE_DNS_API_TOKEN_FILE = secrets.cloudflare-dns.path;
+ };
+ };
+ };
+
+ defaultHost = domain: certificate: base: log: {
+ default = true;
+ serverName = domain;
+ forceSSL = true;
+ useACMEHost = certificate;
+
+ root = base;
+ extraConfig = ''
+ access_log /var/log/nginx/${log}.access.log json_combined;
+ '';
+ };
+
+in
+{
+ imports = [ ./tailnet.nix ];
+
+ options.foundation.www = {
+ enable = mkEnableOption "www server";
+ public = mkEnableOption "public access through rnrd.eu url";
+
+ defaultPage = mkOption {
+ type = lib.types.package;
+ default = default-page;
+ };
+ };
+
+ config = mkIf cfg.enable {
+ age.secrets = {
+ cloudflare-dns.file = ../../../secrets/cloudflare-dns.age;
+ };
+
+ security.acme = {
+ acceptTerms = true;
+ # causes issues with tailscale certificates
+ preliminarySelfsigned = false;
+ defaults = {
+ email = "mel@rnrd.eu";
+ # our certificates are really only used with Nginx
+ group = config.services.nginx.group;
+ reloadServices = [ "nginx.service" ];
+ };
+
+ # yes, we generate both certificates, even if they are not
+ # used by every machine, but as long as it doesn't cause
+ # any problems... :)
+ certs = mergeAttrsList [
+ (certificate "rnrd.eu")
+ (certificate "rnrd.fyi")
+ ];
+ };
+
+ services.nginx = {
+ enable = true;
+ recommendedGzipSettings = true;
+ recommendedOptimisation = true;
+ recommendedProxySettings = true;
+ recommendedTlsSettings = true;
+
+ statusPage = true;
+
+ commonHttpConfig = ''
+ log_format json_combined escape=json '{'
+ '"time_local":"$time_local",'
+ '"remote_addr":"$remote_addr",'
+ '"remote_user":"$remote_user",'
+ '"request":"$request",'
+ '"status": "$status",'
+ '"body_bytes_sent":"$body_bytes_sent",'
+ '"request_length":"$request_length",'
+ '"request_time":"$request_time",'
+ '"http_referrer":"$http_referer",'
+ '"http_user_agent":"$http_user_agent",'
+ '"upstream_response_time":"$upstream_response_time",'
+ '"upstream_addr":"$upstream_addr",'
+ '"upstream_status":"$upstream_status"'
+ '}';
+ access_log /var/log/nginx/access.log json_combined;
+ error_log /var/log/nginx/error.log warn;
+ '';
+
+ virtualHosts = {
+ base = mkIf cfg.public (defaultHost rnrdUrl "rnrd.eu" cfg.defaultPage "base");
+ };
+ };
+ };
+}
diff --git a/modules/foundation/www/tailnet.nix b/modules/foundation/www/tailnet.nix
new file mode 100644
index 0000000..ff2410c
--- /dev/null
+++ b/modules/foundation/www/tailnet.nix
@@ -0,0 +1,121 @@
+# NOTE: the tailnet virtual host and it's certificate management
+# has been mostly superseded by the `rnrd.fyi` domain, allowing
+# for both vastly simpler certificate requesting and subdomains,
+# which tailscale does not support for their magicdns product.
+{
+ me,
+ config,
+ lib,
+ pkgs,
+ ...
+}:
+
+let
+ cfg = config.foundation.www;
+
+ rnrdInternalUrl = if me.is.renard then "rnrd.fyi" else "${me.name}.rnrd.fyi";
+
+ oneWeekInSeconds = 7 * 24 * 60 * 60;
+
+ tailscaleRenewScript = pkgs.writeShellScript "tailscale-cert-renew" ''
+ set -euxo pipefail
+
+ check_validity() {
+ pem=$1
+ ${pkgs.openssl}/bin/openssl x509 \
+ -checkend ${toString oneWeekInSeconds} \
+ -noout <$pem
+ }
+
+ try_renew() {
+ ${pkgs.tailscale}/bin/tailscale cert \
+ --cert-file certificates/fullchain.pem \
+ --key-file certificates/key.pem \
+ ${me.tailscale.domain}
+ }
+
+ cut_out_certificate_authority() {
+ fullchain=$1
+ buf=""
+ while read LINE; do
+ if [[ $LINE == *"BEGIN CERTIFICATE"* ]]; then
+ buf=""
+ fi
+ buf="$buf$LINE"$'\n'
+ done < $fullchain
+ echo "$buf"
+ }
+
+ install_certificates() {
+ touch out/renewed
+ cp -vp 'certificates/fullchain.pem' out/fullchain.pem
+ cp -vp 'certificates/key.pem' out/key.pem
+ ln -sf fullchain.pem out/cert.pem
+ cat out/key.pem out/fullchain.pem > out/full.pem
+ cut_out_certificate_authority out/fullchain.pem > out/chain.pem
+ chown 'acme:nginx' out/*
+ chmod 640 out/*
+ }
+
+ if [[ ! -e 'out/fullchain.pem' ]] || ! check_validity out/fullchain.pem; then
+ echo 1>&2 "attempting tailscale certificate renewal..."
+ if ! try_renew; then
+ echo 1>&2 "renewal failed :("
+ exit 1
+ fi
+ install_certificates
+ echo 1>&2 "successfully renewed certificate :)"
+ else
+ echo 1>&2 "renewal not yet necessary."
+ fi
+ '';
+
+in
+{
+ options.foundation.www = {
+ tailnet = lib.mkEnableOption "tailnet internal host";
+ };
+
+ config =
+ lib.mkIf (cfg.enable && cfg.tailnet) {
+ # overwrite default acme behaviour with tailscale
+ systemd.services."acme-${me.tailscale.domain}" = {
+ after = [ "tailscaled.service" ];
+ requires = [ "tailscaled.service" ];
+ serviceConfig = {
+ ExecStart = lib.mkForce "+${tailscaleRenewScript}";
+ };
+ };
+
+ # tailnet internal vhost
+ services.nginx.virtualHosts = {
+ # mostly superceded
+ tailnet = {
+ forceSSL = true;
+ enableACME = true;
+ serverName = me.tailscale.domain;
+ listenAddresses = [ me.tailscale.ip ];
+ # point to the default page, for now!
+ locations."/" = {
+ alias = "${cfg.defaultPage}/";
+ };
+ extraConfig = ''
+ access_log /var/log/nginx/tailnet.access.log json_combined;
+ '';
+ };
+
+ # default page for the `rnrd.fyi` internal domain
+ ${rnrdInternalUrl} = {
+ useACMEHost = "rnrd.fyi";
+ forceSSL = true;
+ listenAddresses = [ me.tailscale.ip ];
+ locations."/" = {
+ alias = "${cfg.defaultPage}/";
+ };
+ extraConfig = ''
+ access_log /var/log/nginx/tailnet.access.log json_combined;
+ '';
+ };
+ };
+ };
+}