feat(silverbullet): init

Silverbullet is a browser-based markdown editor. We want a shared
notepad and have looked at various options (logseq, obsidian, Collabora
Online) but none of them have quite scratched the itch. That's not to
say they aren't great pieces of software - they are, and they have their
place, but they weren't quite what we needed.

We're hoping silverbullet can scratch that itch.

As a Clicks module, silverbullet uses *unstable* rather than your
default nixpkgs. This is because the version in nixpkgs was deemed too
far behind upstream - upstream is moving very fast right now, and we are
willing to trade off a potential bit of stability in exchange for newer
features. This may change in the future.

As per https://silverbullet.md/Authelia, we have exempted silverbullet
from Tailscale auth on some pages. For our instance on `teal` this makes
no difference - we don't have forwarding set up from the outside world
yet. In the future, this will change, and then it might matter a little
more. For anyone importing and using our modules on their own machines,
we hope that conforming to the authentication guide is welcome.

Change-Id: Ib22f45f0bcb0047e23233a06c7a26732fc3256f5
Reviewed-on: https://git.clicks.codes/c/Infra/NixFiles/+/816
Tested-by: Skyler Grey <minion@clicks.codes>
Reviewed-by: Samuel Shuert <coded@clicks.codes>
diff --git a/flake.lock b/flake.lock
index c187503..05a380e 100644
--- a/flake.lock
+++ b/flake.lock
@@ -590,11 +590,11 @@
     },
     "unstable": {
       "locked": {
-        "lastModified": 1722813957,
-        "narHash": "sha256-IAoYyYnED7P8zrBFMnmp7ydaJfwTnwcnqxUElC1I26Y=",
+        "lastModified": 1723637854,
+        "narHash": "sha256-med8+5DSWa2UnOqtdICndjDAEjxr5D7zaIiK4pn0Q7c=",
         "owner": "nixos",
         "repo": "nixpkgs",
-        "rev": "cb9a96f23c491c081b38eab96d22fa958043c9fa",
+        "rev": "c3aa7b8938b17aebd2deecf7be0636000d62a2b9",
         "type": "github"
       },
       "original": {
diff --git a/modules/nixos/clicks/services/silverbullet/default.nix b/modules/nixos/clicks/services/silverbullet/default.nix
new file mode 100644
index 0000000..992186f
--- /dev/null
+++ b/modules/nixos/clicks/services/silverbullet/default.nix
@@ -0,0 +1,71 @@
+# SPDX-FileCopyrightText: 2024 Clicks Codes
+#
+# SPDX-License-Identifier: GPL-3.0-only
+
+{
+  inputs,
+  lib,
+  config,
+  pkgs,
+  system,
+  ...
+}:
+let
+  cfg = config.clicks.services.silverbullet;
+in
+{
+  options.clicks.services.silverbullet = {
+    enable = lib.mkEnableOption "The silverbullet notes server";
+    tailscaleAuth = lib.mkEnableOption "Lock silverbullet to only be accessible on your tailnet";
+    domain = lib.mkOption {
+      type = lib.types.str;
+      description = "The domain to host your silverbullet server on";
+    };
+    addr = lib.mkOption {
+      type = lib.types.str;
+      description = "Where to host silverbullet";
+      default = "127.0.0.1";
+    };
+    port = lib.mkOption {
+      type = lib.types.int;
+      description = "Port to host silverbullet on";
+      default = 1026;
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    clicks = {
+      services.nginx.enable = true;
+      services.nginx.hosts.${cfg.domain} = {
+        routes = {
+          "/" = lib.clicks.nginx.http.reverseProxy cfg.addr cfg.port; # Only this gets locked behind tailscaleAuth - annoying normally but in this case handy
+          "~ /.client/manifest.json$" = lib.clicks.nginx.http.reverseProxy cfg.addr cfg.port;
+          "~ /.client/[a-zA-Z0-9_-]+.png$" = lib.clicks.nginx.http.reverseProxy cfg.addr cfg.port;
+          "~ /service_worker.js$" = lib.clicks.nginx.http.reverseProxy cfg.addr cfg.port;
+        };
+        www = false;
+        authWith = null; # We will set up tailscale auth manually, as we need to exclude some paths from it
+      };
+      services.tailscaleAuth = {
+        enable = true;
+        hosts = [ cfg.domain ];
+      };
+
+      networking.tailscale.enable = lib.mkIf cfg.tailscaleAuth true;
+
+      storage.impermanence.persist.directories = [
+        { directory = config.services.silverbullet.spaceDir; mode = "0700"; defaultPerms.mode = "0700"; }
+      ];
+    };
+
+    services.silverbullet = {
+      enable = true;
+      listenPort = cfg.port;
+      listenAddress = cfg.addr;
+      package = inputs.unstable.legacyPackages.${system}.silverbullet; # Silverbullet moves fast, the version currently in stable is unacceptably out-of-date
+    };
+
+    systemd.services.silverbullet.requires = (if config.clicks.services.nginx.enable then [ "nginx.service" ] else []);
+    systemd.services.silverbullet.after = (if config.clicks.services.nginx.enable then [ "nginx.service" ] else []);
+  };
+}
diff --git a/systems/x86_64-linux/teal/default.nix b/systems/x86_64-linux/teal/default.nix
index 4cceeca..9af45f5 100644
--- a/systems/x86_64-linux/teal/default.nix
+++ b/systems/x86_64-linux/teal/default.nix
@@ -231,6 +231,11 @@
         };
         domain = "fava.clicks.codes";
       };
+      silverbullet = {
+        enable = true;
+        domain = "silverbullet.clicks.codes";
+        tailscaleAuth = true;
+      };
     };
 
     networking.tailscale = {