nixos-dotfiles

nixos-dotfiles

https://git.tonybtw.com/nixos-dotfiles.git git://git.tonybtw.com/nixos-dotfiles.git

updates.

Commit
5fbd2269cfb5da0b4fa8cd3b1e99f9c8bd86522d
Parent
598e272
Author
tonybanters <tonyoutoften@gmail.com>
Date
2026-02-04 08:15:33

Diff

diff --git a/config/nvim/after/plugin/treesitter.lua b/config/nvim/after/plugin/treesitter.lua
index 4713ce0..fdf3ca3 100644
--- a/config/nvim/after/plugin/treesitter.lua
+++ b/config/nvim/after/plugin/treesitter.lua
@@ -19,7 +19,7 @@ local parsers = {
     "json", "python", "ron", "javascript", "haskell", "d", "query",
     "typescript", "tsx", "rust", "zig", "php", "yaml", "html", "css",
     "markdown", "markdown_inline", "bash", "lua", "vim", "vimdoc", "c",
-    "dockerfile", "gitignore", "astro", "nim"
+    "dockerfile", "gitignore", "astro", "nim", "go"
 }
 
 vim.api.nvim_create_user_command("TSInstallAll", function()
diff --git a/config/nvim/plugin/lsp.lua b/config/nvim/plugin/lsp.lua
index 8bbc49e..87b20d6 100644
--- a/config/nvim/plugin/lsp.lua
+++ b/config/nvim/plugin/lsp.lua
@@ -245,12 +245,6 @@ vim.lsp.config['gopls'] = {
     root_markers = { 'go.mod', 'go.work', '.git' },
     capabilities = caps,
     settings = {
-        gopls = {
-            analyses = {
-                unusedparams = true,
-            },
-            staticcheck = true,
-        },
     },
 }
 
diff --git a/config/temacs/config.el b/config/temacs/config.el
index 40b906b..5c62d98 100644
--- a/config/temacs/config.el
+++ b/config/temacs/config.el
@@ -176,7 +176,7 @@
       erc-use-auth-source-for-nickserv-password t)
 
 (setq erc-autojoin-channels-alist
-      '(("libera.chat" "#technicalrenaissance")))
+      '(("znc.tonybtw.com" "#technicalrenaissance")))
 
 (defun my/erc-connect-libera ()
   "Connect to Libera.Chat via ZNC bouncer."
diff --git a/server/default.nix b/server/default.nix
index 2f2adff..5d43281 100644
--- a/server/default.nix
+++ b/server/default.nix
@@ -1,9 +1,11 @@
-{ ... }:
-{
+{...}: {
   imports = [
     ./nginx.nix
     ./xmpp.nix
+    ./xmpp-register.nix
     ./znc.nix
     ./guandanbtw.nix
+    ./matrix.nix
+    ./git.nix
   ];
 }
diff --git a/server/git.nix b/server/git.nix
new file mode 100644
index 0000000..7b1794c
--- /dev/null
+++ b/server/git.nix
@@ -0,0 +1,74 @@
+{
+  config,
+  pkgs,
+  ...
+}: let
+  domain = "git.tonybtw.com";
+  gitRoot = "/srv/git";
+in {
+  services.cgit.main = {
+    enable = true;
+    nginx.virtualHost = domain;
+    scanPath = gitRoot;
+    gitHttpBackend.enable = false;
+    settings = {
+      root-title = "Tony's Git";
+      root-desc = "Personal git repositories";
+      clone-url = "https://${domain}/$CGIT_REPO_URL git://${domain}/$CGIT_REPO_URL";
+      enable-git-config = 1;
+      enable-index-owner = 0;
+      enable-commit-graph = 1;
+      enable-log-filecount = 1;
+      enable-log-linecount = 1;
+      source-filter = "${pkgs.cgit}/lib/cgit/filters/syntax-highlighting.py";
+      about-filter = "${pkgs.cgit}/lib/cgit/filters/about-formatting.sh";
+      readme = ":README.md";
+    };
+  };
+
+  services.nginx.virtualHosts.${domain} = {
+    enableACME = true;
+    forceSSL = true;
+    locations."~ ^/([^/]+)/(HEAD|info/refs|objects|git-upload-pack)$" = {
+      fastcgiParams = {
+        GIT_HTTP_EXPORT_ALL = "";
+        GIT_PROJECT_ROOT = gitRoot;
+        PATH_INFO = "$uri";
+      };
+      extraConfig = ''
+        fastcgi_pass unix:/run/fcgiwrap.sock;
+      '';
+    };
+  };
+
+  services.fcgiwrap.instances.git = {
+    process.user = "git";
+    process.group = "git";
+    socket = {inherit (config.services.nginx) user group;};
+  };
+
+  systemd.services.git-daemon = {
+    description = "Git daemon";
+    wantedBy = ["multi-user.target"];
+    after = ["network.target"];
+    serviceConfig = {
+      ExecStart = "${pkgs.git}/bin/git daemon --reuseaddr --base-path=${gitRoot} --export-all --verbose ${gitRoot}";
+      User = "git";
+      Group = "git";
+    };
+  };
+
+  users.users.git = {
+    isSystemUser = true;
+    group = "git";
+    home = gitRoot;
+    shell = "${pkgs.git}/bin/git-shell";
+  };
+  users.groups.git = {};
+
+  systemd.tmpfiles.rules = [
+    "d ${gitRoot} 0755 git git -"
+  ];
+
+  networking.firewall.allowedTCPPorts = [9418];
+}
diff --git a/server/matrix.nix b/server/matrix.nix
new file mode 100644
index 0000000..f6eecb6
--- /dev/null
+++ b/server/matrix.nix
@@ -0,0 +1,101 @@
+{
+  config,
+  pkgs,
+  lib,
+  ...
+}: let
+  domain = "tonybtw.com";
+  matrixDomain = "matrix.${domain}";
+  clientConfig = {
+    "m.homeserver".base_url = "https://${matrixDomain}";
+    "m.identity_server" = {};
+  };
+  serverConfig = {
+    "m.server" = "${matrixDomain}:443";
+  };
+  mkWellKnown = data: ''
+    default_type application/json;
+    add_header Access-Control-Allow-Origin *;
+    return 200 '${builtins.toJSON data}';
+  '';
+in {
+  services.matrix-synapse = {
+    enable = true;
+    settings = {
+      server_name = domain;
+      public_baseurl = "https://${matrixDomain}";
+
+      listeners = [
+        {
+          port = 8008;
+          bind_addresses = ["127.0.0.1"];
+          type = "http";
+          tls = false;
+          x_forwarded = true;
+          resources = [
+            {
+              names = ["client" "federation"];
+              compress = true;
+            }
+          ];
+        }
+      ];
+
+      database = {
+        name = "psycopg2";
+        allow_unsafe_locale = true;
+        args = {
+          user = "matrix-synapse";
+          database = "matrix-synapse";
+          host = "/run/postgresql";
+        };
+      };
+
+      max_upload_size_mib = 100;
+      url_preview_enabled = true;
+      enable_registration = false;
+      enable_metrics = false;
+      registration_shared_secret_path = "/var/lib/matrix-synapse/registration_secret";
+
+      trusted_key_servers = [
+        {
+          server_name = "matrix.org";
+        }
+      ];
+    };
+  };
+
+  services.postgresql = {
+    enable = true;
+    ensureDatabases = ["matrix-synapse"];
+    ensureUsers = [
+      {
+        name = "matrix-synapse";
+        ensureDBOwnership = true;
+      }
+    ];
+  };
+
+  services.nginx.virtualHosts.${domain} = {
+    enableACME = true;
+    forceSSL = true;
+    locations."= /.well-known/matrix/server".extraConfig = mkWellKnown serverConfig;
+    locations."= /.well-known/matrix/client".extraConfig = mkWellKnown clientConfig;
+  };
+
+  services.nginx.virtualHosts.${matrixDomain} = {
+    enableACME = true;
+    forceSSL = true;
+    locations."/" = {
+      proxyPass = "http://127.0.0.1:8008";
+      extraConfig = ''
+        proxy_set_header X-Forwarded-For $remote_addr;
+        proxy_set_header X-Forwarded-Proto $scheme;
+        proxy_set_header Host $host;
+        client_max_body_size 100M;
+      '';
+    };
+  };
+
+  networking.firewall.allowedTCPPorts = [8448];
+}
diff --git a/server/xmpp-register.nix b/server/xmpp-register.nix
new file mode 100644
index 0000000..7b7960b
--- /dev/null
+++ b/server/xmpp-register.nix
@@ -0,0 +1,66 @@
+{
+  config,
+  pkgs,
+  lib,
+  ...
+}: let
+  domain = "xmpp.tonybtw.com";
+  dataDir = "/var/lib/xmpp-register";
+  phpUser = "xmpp-register";
+  phpGroup = "xmpp-register";
+
+  phpApp = pkgs.runCommand "xmpp-register-app" {} ''
+    mkdir -p $out
+    cp ${./xmpp-register/index.php} $out/index.php
+  '';
+in {
+  services.phpfpm.pools.xmpp-register = {
+    user = phpUser;
+    group = phpGroup;
+    settings = {
+      "listen.owner" = config.services.nginx.user;
+      "listen.group" = config.services.nginx.group;
+      "pm" = "ondemand";
+      "pm.max_children" = 4;
+      "pm.process_idle_timeout" = "10s";
+    };
+    phpOptions = ''
+      session.save_path = "${dataDir}/sessions"
+    '';
+  };
+
+  services.nginx.virtualHosts.${domain} = {
+    forceSSL = true;
+    useACMEHost = domain;
+
+    locations."= /register" = {
+      return = "301 /register/";
+    };
+
+    locations."^~ /register/" = {
+      extraConfig = ''
+        fastcgi_pass unix:${config.services.phpfpm.pools.xmpp-register.socket};
+        include ${pkgs.nginx}/conf/fastcgi_params;
+        fastcgi_param SCRIPT_FILENAME ${phpApp}/index.php;
+      '';
+    };
+  };
+
+  users.users.${phpUser} = {
+    isSystemUser = true;
+    group = phpGroup;
+    home = dataDir;
+  };
+  users.groups.${phpGroup} = {};
+
+  systemd.tmpfiles.rules = [
+    "d ${dataDir} 0750 ${phpUser} ${phpGroup} -"
+    "d ${dataDir}/sessions 0750 ${phpUser} ${phpGroup} -"
+  ];
+
+  security.sudo.extraConfig = ''
+    ${phpUser} ALL=(prosody) NOPASSWD: /run/current-system/sw/bin/prosodyctl register *
+  '';
+
+  environment.systemPackages = [pkgs.prosody];
+}
diff --git a/server/xmpp-register/index.php b/server/xmpp-register/index.php
new file mode 100644
index 0000000..cdd3934
--- /dev/null
+++ b/server/xmpp-register/index.php
@@ -0,0 +1,289 @@
+<?php
+define('XMPP_DOMAIN', 'xmpp.tonybtw.com');
+define('DATA_FILE', '/var/lib/xmpp-register/pending.json');
+define('ADMIN_PASS_FILE', '/var/lib/xmpp-register/admin_password');
+define('BASE_PATH', '/register');
+
+session_start();
+
+function load_pending(): array {
+    if (!file_exists(DATA_FILE)) return [];
+    $data = file_get_contents(DATA_FILE);
+    return $data ? json_decode($data, true) : [];
+}
+
+function save_pending(array $pending): void {
+    $dir = dirname(DATA_FILE);
+    if (!is_dir($dir)) mkdir($dir, 0750, true);
+    file_put_contents(DATA_FILE, json_encode($pending, JSON_PRETTY_PRINT));
+}
+
+function get_admin_password(): string {
+    return file_exists(ADMIN_PASS_FILE) ? trim(file_get_contents(ADMIN_PASS_FILE)) : '';
+}
+
+function is_admin(): bool {
+    return isset($_SESSION['admin']) && $_SESSION['admin'] === true;
+}
+
+function redirect(string $path): void {
+    header("Location: " . BASE_PATH . $path);
+    exit;
+}
+
+function url(string $path): string {
+    return BASE_PATH . $path;
+}
+
+function h(string $s): string {
+    return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
+}
+
+$flash = $_SESSION['flash'] ?? null;
+unset($_SESSION['flash']);
+
+function flash(string $type, string $msg): void {
+    $_SESSION['flash'] = ['type' => $type, 'msg' => $msg];
+}
+
+$fullPath = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
+$path = preg_replace('#^' . preg_quote(BASE_PATH, '#') . '#', '', $fullPath) ?: '/';
+$method = $_SERVER['REQUEST_METHOD'];
+
+if ($method === 'POST') {
+    if ($path === '/submit') {
+        $username = strtolower(trim($_POST['username'] ?? ''));
+        $password = $_POST['password'] ?? '';
+        $password_confirm = $_POST['password_confirm'] ?? '';
+        $reason = trim($_POST['reason'] ?? '');
+
+        if (!$username || !preg_match('/^[a-z][a-z0-9._-]{2,29}$/', $username)) {
+            flash('error', 'Username must be 3-30 chars, start with a letter, lowercase alphanumeric/._-');
+            redirect('/');
+        }
+        if (strlen($password) < 8) {
+            flash('error', 'Password must be at least 8 characters.');
+            redirect('/');
+        }
+        if ($password !== $password_confirm) {
+            flash('error', 'Passwords do not match.');
+            redirect('/');
+        }
+        if (!$reason) {
+            flash('error', 'Please tell us why you want to join.');
+            redirect('/');
+        }
+
+        $pending = load_pending();
+        foreach ($pending as $p) {
+            if ($p['username'] === $username) {
+                flash('error', 'A registration for this username is already pending.');
+                redirect('/');
+            }
+        }
+
+        $pending[] = [
+            'id' => bin2hex(random_bytes(8)),
+            'username' => $username,
+            'password' => $password,
+            'reason' => $reason,
+            'timestamp' => date('c'),
+            'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
+        ];
+        save_pending($pending);
+
+        flash('success', 'Registration submitted! You\'ll be notified once approved.');
+        redirect('/');
+    }
+
+    if ($path === '/admin/login') {
+        $pass = $_POST['password'] ?? '';
+        if ($pass && $pass === get_admin_password()) {
+            $_SESSION['admin'] = true;
+            redirect('/admin');
+        }
+        flash('error', 'Invalid password.');
+        redirect('/admin/login');
+    }
+
+    if ($path === '/admin/logout') {
+        unset($_SESSION['admin']);
+        redirect('/');
+    }
+
+    if (preg_match('#^/admin/approve/([a-f0-9]+)$#', $path, $m) && is_admin()) {
+        $id = $m[1];
+        $pending = load_pending();
+        $req = null;
+        foreach ($pending as $i => $p) {
+            if ($p['id'] === $id) {
+                $req = $p;
+                unset($pending[$i]);
+                break;
+            }
+        }
+        if (!$req) {
+            flash('error', 'Request not found.');
+            redirect('/admin');
+        }
+
+        $jid = $req['username'] . '@' . XMPP_DOMAIN;
+        $pass = $req['password'];
+
+        $cmd = sprintf(
+            '/run/wrappers/bin/sudo -u prosody /run/current-system/sw/bin/prosodyctl register %s %s %s 2>&1',
+            escapeshellarg($req['username']),
+            escapeshellarg(XMPP_DOMAIN),
+            escapeshellarg($pass)
+        );
+        $output = shell_exec($cmd);
+        file_put_contents('/var/lib/xmpp-register/debug.log', date('c') . " CMD: $cmd\nOUT: $output\n", FILE_APPEND);
+
+        save_pending(array_values($pending));
+        if (strpos($output, 'User account created') !== false) {
+            flash('success', "User $jid created!");
+        } else {
+            flash('error', "Failed: $output");
+        }
+        redirect('/admin');
+    }
+
+    if (preg_match('#^/admin/deny/([a-f0-9]+)$#', $path, $m) && is_admin()) {
+        $id = $m[1];
+        $pending = load_pending();
+        $pending = array_filter($pending, fn($p) => $p['id'] !== $id);
+        save_pending(array_values($pending));
+        flash('success', 'Registration denied.');
+        redirect('/admin');
+    }
+}
+
+?>
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>XMPP Registration - <?= h(XMPP_DOMAIN) ?></title>
+    <style>
+        :root { --bg:#1a1a2e; --surface:#16213e; --primary:#0f3460; --accent:#e94560; --text:#eee; --muted:#aaa; --ok:#4ade80; --err:#f87171; }
+        * { box-sizing:border-box; margin:0; padding:0; }
+        body { font-family:system-ui,sans-serif; background:var(--bg); color:var(--text); min-height:100vh; display:flex; justify-content:center; padding:2rem 1rem; }
+        .container { width:100%; max-width:500px; }
+        h1 { text-align:center; color:var(--accent); margin-bottom:.5rem; }
+        .sub { text-align:center; color:var(--muted); margin-bottom:2rem; }
+        .card { background:var(--surface); border-radius:12px; padding:2rem; }
+        .form-group { margin-bottom:1.5rem; }
+        label { display:block; margin-bottom:.5rem; color:var(--muted); font-size:.9rem; }
+        input,textarea { width:100%; padding:.75rem 1rem; border:2px solid var(--primary); border-radius:8px; background:var(--bg); color:var(--text); font-size:1rem; }
+        input:focus,textarea:focus { outline:none; border-color:var(--accent); }
+        textarea { resize:vertical; min-height:80px; }
+        .hint { font-size:.8rem; color:var(--muted); margin-top:.25rem; }
+        .addon-wrap { display:flex; }
+        .addon-wrap input { border-radius:8px 0 0 8px; }
+        .addon { background:var(--primary); padding:.75rem 1rem; border-radius:0 8px 8px 0; color:var(--muted); font-size:.9rem; }
+        button,.btn { display:inline-block; padding:.75rem 1.5rem; background:var(--accent); color:#fff; border:none; border-radius:8px; font-size:1rem; font-weight:600; cursor:pointer; text-decoration:none; }
+        button:hover,.btn:hover { opacity:.9; }
+        .btn-ok { background:var(--ok); color:var(--bg); }
+        .btn-no { background:var(--err); }
+        .btn-sm { padding:.5rem 1rem; font-size:.875rem; }
+        .flash { padding:1rem; border-radius:8px; margin-bottom:1.5rem; }
+        .flash-error { background:rgba(248,113,113,.2); border:1px solid var(--err); color:var(--err); }
+        .flash-success { background:rgba(74,222,128,.2); border:1px solid var(--ok); color:var(--ok); }
+        .nav { margin-bottom:1.5rem; }
+        .nav a { color:var(--accent); }
+        .pending { background:var(--surface); border-radius:8px; padding:1rem; margin-bottom:1rem; }
+        .pending-head { display:flex; justify-content:space-between; margin-bottom:.5rem; }
+        .pending-user { font-weight:600; color:var(--accent); }
+        .pending-meta { font-size:.8rem; color:var(--muted); }
+        .pending-reason { margin:.5rem 0; padding:.5rem; background:var(--bg); border-radius:4px; font-size:.9rem; }
+        .pending-actions { display:flex; gap:.5rem; }
+        .empty { text-align:center; color:var(--muted); padding:2rem; }
+    </style>
+</head>
+<body>
+<div class="container">
+<?php if ($flash): ?>
+    <div class="flash flash-<?= h($flash['type']) ?>"><?= h($flash['msg']) ?></div>
+<?php endif; ?>
+
+<?php if ($path === '/admin/login'): ?>
+    <h1>Admin Login</h1>
+    <p class="sub">Enter admin password</p>
+    <div class="card">
+        <form method="post" action="<?= url('/admin/login') ?>">
+            <div class="form-group">
+                <label>Password</label>
+                <input type="password" name="password" required autofocus>
+            </div>
+            <button type="submit">Login</button>
+        </form>
+    </div>
+    <p class="nav" style="margin-top:1rem;text-align:center;"><a href="<?= url('/') ?>">Back to registration</a></p>
+
+<?php elseif ($path === '/admin' && is_admin()): ?>
+    <h1>Admin Panel</h1>
+    <p class="sub">Pending registration requests</p>
+    <p class="nav"><a href="<?= url('/') ?>">Registration page</a> | <form method="post" action="<?= url('/admin/logout') ?>" style="display:inline;"><button type="submit" class="btn btn-sm" style="background:var(--primary);">Logout</button></form></p>
+    <?php $pending = load_pending(); ?>
+    <?php if (empty($pending)): ?>
+        <div class="card empty">No pending requests.</div>
+    <?php else: ?>
+        <?php foreach ($pending as $p): ?>
+        <div class="pending">
+            <div class="pending-head">
+                <span class="pending-user"><?= h($p['username']) ?>@<?= h(XMPP_DOMAIN) ?></span>
+                <span class="pending-meta"><?= h(substr($p['timestamp'], 0, 10)) ?></span>
+            </div>
+            <div class="pending-meta">IP: <?= h($p['ip']) ?></div>
+            <div class="pending-reason"><?= h($p['reason']) ?></div>
+            <div class="pending-actions">
+                <form method="post" action="<?= url('/admin/approve/' . h($p['id'])) ?>">
+                    <button type="submit" class="btn btn-sm btn-ok">Approve</button>
+                </form>
+                <form method="post" action="<?= url('/admin/deny/' . h($p['id'])) ?>">
+                    <button type="submit" class="btn btn-sm btn-no">Deny</button>
+                </form>
+            </div>
+        </div>
+        <?php endforeach; ?>
+    <?php endif; ?>
+
+<?php elseif ($path === '/admin'): ?>
+    <?php redirect('/admin/login'); ?>
+
+<?php else: ?>
+    <h1>Join <?= h(XMPP_DOMAIN) ?></h1>
+    <p class="sub">Request an XMPP account</p>
+    <div class="card">
+        <form method="post" action="<?= url('/submit') ?>">
+            <div class="form-group">
+                <label>Username</label>
+                <div class="addon-wrap">
+                    <input type="text" name="username" pattern="[a-z][a-z0-9._-]{2,29}" required autofocus>
+                    <span class="addon">@<?= h(XMPP_DOMAIN) ?></span>
+                </div>
+                <p class="hint">3-30 chars, starts with letter, lowercase only</p>
+            </div>
+            <div class="form-group">
+                <label>Password</label>
+                <input type="password" name="password" minlength="8" required>
+                <p class="hint">At least 8 characters</p>
+            </div>
+            <div class="form-group">
+                <label>Confirm Password</label>
+                <input type="password" name="password_confirm" minlength="8" required>
+            </div>
+            <div class="form-group">
+                <label>Why do you want to join?</label>
+                <textarea name="reason" required placeholder="Tell us a bit about yourself..."></textarea>
+            </div>
+            <button type="submit">Submit Request</button>
+        </form>
+    </div>
+    <p class="nav" style="margin-top:1rem;text-align:center;"><a href="<?= url('/admin') ?>">Admin</a></p>
+<?php endif; ?>
+</div>
+</body>
+</html>
diff --git a/server/xmpp.nix b/server/xmpp.nix
index 412d9c4..2a5405f 100644
--- a/server/xmpp.nix
+++ b/server/xmpp.nix
@@ -81,15 +81,22 @@ in {
     defaults.email = "tony@tonybtw.com";
 
     certs.${domain} = {
-      group = "prosody";
-      listenHTTP = ":80";
+      group = "certs";
+      webroot = "/var/lib/acme/acme-challenge";
       postRun = "systemctl reload prosody.service";
       extraDomainNames = [mucDomain uploadDomain];
     };
   };
 
+  users.groups.certs.members = ["prosody" "nginx"];
+
+  services.nginx.virtualHosts.${domain} = {
+    locations."/.well-known/acme-challenge" = {
+      root = "/var/lib/acme/acme-challenge";
+    };
+  };
+
   networking.firewall.allowedTCPPorts = [
-    80
     5222
     5269
     5281
@@ -103,4 +110,5 @@ in {
     iptables -A INPUT -p tcp --dport 5222 -m state --state NEW -m recent --set
     iptables -A INPUT -p tcp --dport 5222 -m state --state NEW -m recent --update --seconds 60 --hitcount 10 -j DROP
   '';
+
 }
diff --git a/server/znc.nix b/server/znc.nix
index 04005e9..2cdafd8 100644
--- a/server/znc.nix
+++ b/server/znc.nix
@@ -1,5 +1,4 @@
-{ config, ... }:
-{
+{config, ...}: {
   services.znc = {
     enable = true;
     openFirewall = true;
@@ -7,7 +6,7 @@
     useLegacyConfig = false;
     config = {
       SSLCertFile = "/var/lib/acme/znc.tonybtw.com/full.pem";
-      LoadModule = [ "webadmin" "adminlog" ];
+      LoadModule = ["webadmin" "adminlog"];
       Listener.l = {
         Port = 6697;
         SSL = true;
@@ -24,10 +23,10 @@
         AltNick = "tonybtw_";
         Ident = "tony";
         RealName = "tony";
-        LoadModule = [ "chansaver" "controlpanel" ];
+        LoadModule = ["chansaver" "controlpanel"];
         Network.libera = {
           Server = "irc.libera.chat +6697";
-          LoadModule = [ "simple_away" "sasl" "nickserv" "log" ];
+          LoadModule = ["simple_away" "sasl" "nickserv" "log"];
           Chan = {
             "#technicalrenaissance" = {};
           };
@@ -54,7 +53,7 @@
   };
 
   security.acme.certs."znc.tonybtw.com".group = "znc";
-  users.users.nginx.extraGroups = [ "znc" ];
+  users.users.nginx.extraGroups = ["znc"];
 
-  networking.firewall.allowedTCPPorts = [ 6697 ];
+  networking.firewall.allowedTCPPorts = [6697];
 }