CCE

The Wobserver's Edge Server

Contents

The Wobserver is currently in my living room running on my Homelab Build, so how do you view these pages? Surely my home IP address is not blasted on to the web via DynDNS, right? Right.

The Edge Server is a small VM co-located with AS64241 in beautiful Seattle, Washington. Yours could be on AWS, or DigitalOcean, or Hetzner, or whatever, man; it could also be your home IP address blasted on to the web via DynDNS, but it doesn't need to be. The Edge Server can establish an encrypted connection to the Wobserver using Tailscale. SSL can be terminated by an NGINX and proxied to the Nginx on the far side, or transparently proxied for termination within the Wobserver.

Base NixOS Server Manifest

This thing is "just" an Arroyo System, it pulls its configuration from my Knowledge Base.

nix:tangle ~/nix/nixos/edge.nix:noweb yes:mkdirp yes
{ config, pkgs, lib, ... }:

{
  services.nginx.on-retreat.enable = false;
  services.nginx.on-retreat.returnDate = "2 November";

  services.tailscale.useRoutingFeatures = "server";
  networking.firewall.logRefusedConnections = false;

  imports = [
    # <<arroyo_nixos_imports()>>
    
    ../nixos/akkoma-media-proxy.nix
  ];
  
  home-manager.users.rrix.imports = [
    # <<arroyo_home-manager_imports()>>
  ];
  
  system.stateVersion = lib.mkDefault "24.11";

  home-manager.users.rrix = {
    home.stateVersion = config.system.stateVersion;
    # don't ship pinentry-qt
    services.gpg-agent.pinentryPackage = lib.mkForce pkgs.pinentry-curses;
  };

  services.prometheus.exporters.node = {
    enable = true;
    enabledCollectors = [ "systemd" ];
  };

  services.openssh.enable = true;

  environment.systemPackages = with pkgs; [
    pkgs.htop
  ];
}

Minimizing the Edge Server's NixOS Closure Size

nix:tangle ~/nix/nixos/minimize-edge.nix
{ lib, pkgs, ... }:

{

The edge server is a 10gb-disk VM that only ever receives pushed deployments; it does not build things and nothing runs nixpkgs#... there. The flake-registry pin copies the full nixpkgs source into the closure (measured ~200MB / 90k+ inodes on this input).

nix:tangle ~/nix/nixos/minimize-edge.nix
  nixpkgs.flake.setFlakeRegistry = false;
  nixpkgs.flake.setNixPath = false;

Cut everything that ships docs, man pages, or pulls the nixpkgs source into the closure.

nix:tangle ~/nix/nixos/minimize-edge.nix
  documentation.enable = false;
  documentation.nixos.enable = false;
  documentation.man.enable = false;
  documentation.doc.enable = false;

defaultPackages pull grub/micro-code/etc.

nix:tangle ~/nix/nixos/minimize-edge.nix
  environment.defaultPackages = [];

growpart needs cloud-utils + python3 in the closure; autoResize already grows the filesystem post-build, and the partition table is baked into the qcow2 at the size it will ever be. mkForce because hosts/king-mountain sets it true for the generic image.

nix:tangle ~/nix/nixos/minimize-edge.nix
  boot.growPartition = lib.mkForce false;

nixos-rebuild + installer tooling pulls nixos-rebuild-ng (python3 runtime, ~140MB) into the closure; deployments are pushed from the build host, this VM never rebuilds itself.

nix:tangle ~/nix/nixos/minimize-edge.nix
  system.disableInstallerTools = lib.mkForce true;

keep the store from holding old generations on a small disk; nixpkgs.nix sets 30d for big hosts, edge wants aggressive gc.

nix:tangle ~/nix/nixos/minimize-edge.nix
  nix.gc.options = lib.mkForce "--delete-older-than 7d";
nix:tangle ~/nix/nixos/minimize-edge.nix
  i18n.supportedLocales = [ "en_US.UTF-8/UTF-8" "en_GB.UTF-8/UTF-8" "C.UTF-8/UTF-8" ];

home-manager ships a manual + reference manpages + mime-info in its user env by default; none of that is useful on a headless VM. and don't ship pinentry-qt

nix:tangle ~/nix/nixos/minimize-edge.nix
  home-manager.users.rrix = {
    manual.manpages.enable = false;
    manual.html.enable = false;
    manual.json.enable = false;
    xdg.mime.enable = false;
    xdg.mimeApps.enable = false;
    programs.man.enable = false;
    services.gpg-agent.pinentryPackage = lib.mkForce pkgs.pinentry-curses;
  };

}

Helpers

lua#+name: arroyo_nixos_imports:eval arroyo:results raw
local modules = arroyo.nixos_modules("edge")
local lines = {}
for i, m in ipairs(modules) do
    table.insert(lines, "../../" .. m)
end
return table.concat(lines, "\n")
lua#+name: arroyo_home-manager_imports:eval arroyo:results raw
local modules = arroyo.home_modules("edge")
local lines = {}
for i, m in ipairs(modules) do
    table.insert(lines, "../../" .. m)
end
return table.concat(lines, "\n")

The Edge Server's Base Nginx Forwarding configuration

The Edge Server mostly exists to serve My HTTP services and My Websites from The Wobserver to the web from a stable public IP address.

It's set up as a VM King Mountain right now but can quickly come up anywhere you can get a NixOS. Build a global content distribution network on the back of Tailscale's peering agreements, today!

nix:tangle ~/nix/nixos/nginx-edge.nix:noweb yes
{ config, pkgs, lib, ... }:

let
  enabled = config.services.nginx.on-retreat.enable;
  returnDate = config.services.nginx.on-retreat.returnDate;
  retreatPage = pkgs.replaceVars ../files/on-retreat.html {
    inherit returnDate;
  };
in
{
  options.services.nginx.on-retreat = with lib; {
    enable = mkEnableOption { name = "turn off sites while offline"; };
    returnDate = mkOption { type = types.str; default = ""; };
  };

  config = {
    networking.firewall.allowedTCPPorts = [ 80 22 443 ];

    services.logrotate = {
      enable = true;
      settings.nginx = {
        frequency = "daily";
        rotate = 365;
        compress = true;
        delaycompress = true;
      };
    };

    services.nginx.virtualHosts."fontkeming.fail" = {
      serverAliases = [
        <<mkVirtualHosts()>>
      ];

      forceSSL = true;

      locations = lib.mkMerge [
        # Retreat mode: serve the 503 page
        (lib.mkIf enabled {
          "/" = {
            return = "503";
            extraConfig = ''
              error_page 503 /503.html;
            '';
          };
        })

        # Normal mode: proxy everything
        (lib.mkIf (!enabled) {
          "/" = {
            proxyPass = "http://last-bank";
            extraConfig = ''
              proxy_set_header X-Real-IP $remote_addr;
              proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
              proxy_set_header X-Forwarded-Proto $scheme;
              proxy_set_header X-Forwarded-Host $http_host;
              proxy_set_header Host $host;
              proxy_read_timeout 600s;
              proxy_connect_timeout 600s;

              proxy_http_version 1.1;
              proxy_set_header Upgrade $http_upgrade;
              proxy_set_header Connection "upgrade";
            '';
          };
        })

        {
          "=/503.html" = {
            alias = retreatPage;
            extraConfig = ''
              internal;
              add_header Content-Type text/html;
            '';
          };
        }
      ];
    };
  };
}

build a new qcow2

On Retreat page

html:comments none:tangle ~/nix/files/on-retreat.html
<!DOCTYPE html>
<html>
    <head>
        <title>The Arcology Site Network is temporarily offline</title>
    </head>
    <body>
        <h1>Ryan Rix is offline and AFK right now</h1>

        <p>They will be returning from a meditation retreat on <b>@returnDate@</b> and his sites will be offline until then. Thank you for understanding.</p>
        
        <p>Please contact <a href="https://bluecliffzen.org">Blue Cliff Zen Center</a> if you need to get ahold of them despite this. Be good and be good at it.</p>
    </body>
</html>

Helpers for Hosts

lua#+name: mkVirtualHosts:eval arroyo:results raw:var tbl=nginx-common.org:hosts
local lines = {}
for i, row in ipairs(tbl) do
    table.insert(lines, '"' .. row[1] .. '"')
end
return table.concat(lines, "\n")
"fontkeming.fail"
"dns.fontkeming.fail"
"docker.fontkeming.fail"
"scanner.fontkeming.fail"
"vault.whatthefuck.computer"
"kickass.systems"
"thelionsrear.com"
"arcology.garden"
"engine.arcology.garden"
"thechanceencounter.com"
"cce.whatthefuck.computer"
"whatthefuck.computer"
"files.fontkeming.fail"
"notes.whatthefuck.computer"
"i.notes.whatthefuck.computer"
"bag.fontkeming.fail"
"ring.whatthefuck.computer"
"media.whatthefuck.computer"
"feeds.whatthefuck.computer"
"dongiverse.com"
"links.fontkeming.fail"