diff options
| author | Collin Williams <96917990+bluedragon1221@users.noreply.github.com> | 2026-10-02 11:23:45 -0500 |
|---|---|---|
| committer | Collin Williams <96917990+bluedragon1221@users.noreply.github.com> | 2026-10-02 11:23:45 -0500 |
| commit | b768ad0ca4208e8b3588b881067c43d85246e967 (patch) | |
| tree | 460dca5c6568307efc94e256186346be0271478a | |
| parent | 84364a52c3d7ca2f550cb3f7af56848405902bee (diff) | |
experiment with modular services; more unix domain sockets, clean up old code
26 files changed, 478 insertions, 1015 deletions
@@ -183,7 +183,7 @@ "geolite-db": { "flake": false, "locked": { - "narHash": "sha256-Na5E1pifzdXc9jGDMh53zzvQG571QMfKldCtulw+ibE=", + "narHash": "sha256-rsuzb27O5bkv5U2s6+AClaY/9G3SIMXiEZwJYnRrsDo=", "type": "file", "url": "https://github.com/P3TERX/GeoLite.mmdb/releases/latest/download/GeoLite2-City.mmdb" }, @@ -233,22 +233,6 @@ "type": "github" } }, - "keys-repo": { - "flake": false, - "locked": { - "lastModified": 1789787816, - "narHash": "sha256-seSEweu5KYkVwWqLhgk5loEX/3gN+5fw35x2+dhH3ZM=", - "ref": "refs/heads/main", - "rev": "0d5e2f739dd549db5c31c23209a0bee02abec1da", - "revCount": 14, - "type": "git", - "url": "ssh://git@github.com/modernidiots-org/keys" - }, - "original": { - "type": "git", - "url": "ssh://git@github.com/modernidiots-org/keys" - } - }, "lanzaboote": { "inputs": { "crane": "crane", @@ -370,12 +354,10 @@ "fx-autoconfig": "fx-autoconfig", "geolite-db": "geolite-db", "hjem": "hjem", - "keys-repo": "keys-repo", "lanzaboote": "lanzaboote", "nix-index-database": "nix-index-database", "nixos-facter-modules": "nixos-facter-modules", "nixpkgs": "nixpkgs", - "shared_assets": "shared_assets", "tmux-tsunami": "tmux-tsunami", "uc-css-js": "uc-css-js" } @@ -401,18 +383,6 @@ "type": "github" } }, - "shared_assets": { - "flake": false, - "locked": { - "path": "git+ssh:/git@ganymede:shared-assets", - "type": "path" - }, - "original": { - "path": "git+ssh:/git@ganymede:shared-assets", - "type": "path" - }, - "parent": [] - }, "systems": { "locked": { "lastModified": 1681028828, diff --git a/hosts/ganymede/caddy.nix b/hosts/ganymede/caddy.nix index 96efc07..d98a8fc 100644 --- a/hosts/ganymede/caddy.nix +++ b/hosts/ganymede/caddy.nix @@ -90,7 +90,7 @@ }; collinux.services.glance.homelabServices."website" = { - url = "https://williamsfam.us.com"; + url = "https://collin.williamsfam.us.com"; icon = "mdi:web"; }; } diff --git a/hosts/ganymede/config.nix b/hosts/ganymede/config.nix index f1804e9..8e35c45 100644 --- a/hosts/ganymede/config.nix +++ b/hosts/ganymede/config.nix @@ -44,11 +44,8 @@ port = 8076; }; cgit.enable = true; - filebrowser.enable = true; - glance = { - enable = true; - port = 8081; - }; + dufs.enable = true; + glance.enable = true; caddy = { enable = true; diff --git a/hosts/ganymede/networking.nix b/hosts/ganymede/networking.nix index 72eb026..7db36c2 100644 --- a/hosts/ganymede/networking.nix +++ b/hosts/ganymede/networking.nix @@ -15,17 +15,16 @@ in { dhcpcd.enable = false; useDHCP = false; networkmanager.enable = false; - useNetworkd = true; + nameservers = ["127.0.0.1"]; + resolvconf.enable = true; + firewall = { enable = true; checkReversePath = "loose"; }; - resolvconf.enable = true; - nameservers = ["127.0.0.1"]; - wireless = { enable = true; secretsFile = static.pskFile; @@ -56,12 +55,10 @@ in { }; }; + # for next time I break networking... networks."20-teather" = { - # for next time I break networking... name = "enp*"; - networkConfig = { - DHCP = "yes"; - }; + networkConfig.DHCP = "yes"; }; }; }; diff --git a/hosts/mercury/hjem.nix b/hosts/mercury/hjem.nix index 307317a..e27e87c 100644 --- a/hosts/mercury/hjem.nix +++ b/hosts/mercury/hjem.nix @@ -1,4 +1,8 @@ -{pkgs, ...}: let +{ + pkgs, + lib, + ... +}: let rust-shell = let rustProfile = pkgs.buildEnv { name = "rust-profile"; @@ -26,16 +30,55 @@ export PYTHONSTARTUP="" exec $SHELL ''; + + smallObsidian = let + pname = "obsidian"; + version = "1.13.7"; + in + pkgs.stdenv.mkDerivation { + inherit pname version; + + src = pkgs.fetchurl { + url = "https://github.com/obsidianmd/obsidian-releases/releases/download/v${version}/obsidian-${version}.tar.gz"; + hash = "sha256-08vjdcv6QCTbGRC5gZFkn0E0xcSK7l5gtudxOYfc2yg="; + }; + nativeBuildInputs = with pkgs; [ + autoPatchelfHook + makeWrapper + imagemagick + asar + ]; + installPhase = '' + runHook preInstall + mkdir -p $out/bin + + # Mark Obsidian's app:// scheme `corsEnabled` to fix the internal PDF + # viewer; see https://github.com/NixOS/nixpkgs/pull/525772 for details. + # Remove once upstream registers the scheme with `corsEnabled`. + asar extract resources/app.asar app-src + substituteInPlace app-src/main.js \ + --replace-fail "supportFetchAPI: true," "supportFetchAPI: true, corsEnabled: true," + asar pack app-src resources/app.asar + + makeWrapper ${pkgs.electron}/bin/electron $out/bin/obsidian \ + --add-flags $out/share/obsidian/app.asar \ + --add-flags "\''${NIXOS_OZONE_WL:+\''${WAYLAND_DISPLAY:+--ozone-platform=wayland --enable-wayland-ime=true --wayland-text-input-version=3}}" + install -m 755 -D obsidian-cli $out/bin/obsidian-cli + install -m 444 -D resources/app.asar $out/share/obsidian/app.asar + install -m 444 -D resources/obsidian.asar $out/share/obsidian/obsidian.asar + runHook postInstall + ''; + }; in { packages = with pkgs; [ anki libreoffice-qt musescore vscodium + smallObsidian noctalia-shell - prismlauncher mpv irssi diff --git a/hosts/mercury/networking.nix b/hosts/mercury/networking.nix index cc1f995..dc0a3f3 100644 --- a/hosts/mercury/networking.nix +++ b/hosts/mercury/networking.nix @@ -18,25 +18,11 @@ systemd.network = { enable = true; - networks."11-default" = { name = "wl*"; networkConfig.DHCP = "yes"; - - # never accept dhcp dns - # dhcpV4Config.UseDNS = "no"; - # dhcpV6Config.UseDNS = "no"; }; }; - services.resolved = { - enable = true; - settings.Resolve = { - # DNSOverTLS = true; - # DNSSEC = "allow-downgrade"; - - # LLMNR = false; - # MulticastDNS = false; - }; - }; + services.resolved.enable = true; } diff --git a/hosts/mercury/nixos.nix b/hosts/mercury/nixos.nix index ff386cd..4eab7b4 100644 --- a/hosts/mercury/nixos.nix +++ b/hosts/mercury/nixos.nix @@ -22,15 +22,16 @@ hosts |> (lib.mapAttrsToList (hostname: hostAttrs: if hostAttrs ? ip - then '' - Host ${hostname} - ${ + then + '' + Host ${hostname} + HostName ${hostAttrs.ip} + '' + + ( if hostAttrs ? jump - then "ProxyJump ${hostAttrs.jump}" + then " ProxyJump ${hostAttrs.jump}" else "" - } - HostName ${hostAttrs.ip} - '' + ) else "")) |> (builtins.filter (s: s != "")) |> (lib.concatStringsSep "\n"); diff --git a/modules/services/nixos/btopweb.nix b/modules/services/nixos/btopweb.nix index 9c78295..74a2447 100644 --- a/modules/services/nixos/btopweb.nix +++ b/modules/services/nixos/btopweb.nix @@ -6,46 +6,89 @@ }: let cfg = config.collinux.services.btopweb; - btopSettings = pkgs.writeText "btop.conf" '' - color_theme = "tomorrow-night" - enable_mouse = true - background_update = false - - proc_tree = true - proc_colors = true - ''; -in { - config = lib.mkIf cfg.enable { - users.groups."btopweb" = {}; - users.users."btopweb" = { - isSystemUser = true; - group = "btopweb"; + modularService = { + lib, + config, + options, + ... + }: let + cfg = config.btopweb; + in { + options.btopweb = { + ttydPackage = lib.mkOption { + description = "the `ttyd` package"; + type = lib.types.package; + }; + btopPackage = lib.mkOption { + description = "the `btop` package"; + type = lib.types.package; + }; + btopSettings = lib.mkOption { + description = "configuration file for btop"; + type = lib.types.lines; + }; }; - users.users.caddy.extraGroups = ["btopweb"]; - systemd.services."btopweb" = { - description = "Host btop on a website"; - restartIfChanged = true; - wants = ["network-online.target"]; - after = ["network-online.target"]; - wantedBy = ["multi-user.target"]; + config = + { + configData."btop.conf".text = cfg.btopSettings; + process.argv = [ + (lib.getExe cfg.ttydPackage) + "-W" + "-i" + "/run/btopweb/ttyd.sock" + "-t" + "renderType=canvas" + "-t" + "fontSize=16" + (lib.getExe cfg.btopPackage) + "-c" + config.configData."btop.conf".path + ]; + } + // lib.optionalAttrs (options ? systemd) { + systemd.service = { + description = "Host btop on a website"; + wants = ["network-online.target"]; + after = ["network-online.target"]; + wantedBy = ["multi-user.target"]; + + serviceConfig = { + DynamicUser = true; + Group = "caddy"; - serviceConfig = { - User = "btopweb"; - Group = "btopweb"; - Type = "simple"; + RuntimeDirectory = "btopweb"; + RuntimeDirectoryMode = "0750"; + UMask = "0002"; - RuntimeDirectory = "btopweb"; - RuntimeDirectoryMode = "0770"; - UMask = "0007"; + # allow btop to monitor system stats + ProtectProc = "default"; + ProcSubset = "all"; + + ProtectSystem = "strict"; + ProtectHome = true; + PrivateTmp = true; + NoNewPrivileges = true; + + Restart = "on-failure"; + }; + }; + }; + }; +in { + config = lib.mkIf cfg.enable { + system.services."btopweb" = { + imports = [modularService]; + btopweb = { + ttydPackage = pkgs.ttyd; + btopPackage = pkgs.btop; + btopSettings = '' + color_theme = "tomorrow-night" + enable_mouse = true + background_update = false - ExecStart = '' - ${lib.getExe pkgs.ttyd} \ - -W \ - -i /run/btopweb/ttyd.sock \ - -t renderType=canvas \ - -t fontSize=16 \ - ${pkgs.btop}/bin/btop -c ${btopSettings} + proc_tree = true + proc_colors = true ''; }; }; diff --git a/modules/services/nixos/cgit/default.nix b/modules/services/nixos/cgit/default.nix index eaa1682..1da463c 100644 --- a/modules/services/nixos/cgit/default.nix +++ b/modules/services/nixos/cgit/default.nix @@ -63,7 +63,7 @@ in { root-desc=Git repos for my various personal projects readme=:README.md - about-filter=${md2html}/bin/md2html.sh + about-filter=${lib.getExe md2html} source-filter=${pkgs.cgit}/lib/cgit/filters/syntax-highlighting.py head-include=${./cgit-head.html} footer= @@ -72,31 +72,35 @@ in { scan-path=/var/lib/cgit ''; - services.fcgiwrap.instances."cgit" = { - process = { - user = "git"; - group = "git"; + systemd.sockets."fcgiwrap-cgit" = { + wantedBy = ["sockets.target"]; + listenStreams = ["/run/cgit/fcgiwrap.sock"]; + socketConfig = { + SocketMode = "0660"; + SocketGroup = "caddy"; }; + }; - socket = { - user = "caddy"; - group = "caddy"; - type = "unix"; - address = "/run/fcgiwrap-cgit.sock"; + systemd.services."fcgiwrap-cgit" = { + after = ["nss-user-lookup.target"]; + serviceConfig = { + ExecStart = "${lib.getExe pkgs.fcgiwrap} -c 1"; + User = "git"; + Group = "git"; }; }; services.caddy.virtualHosts."git.collin.williamsfam.us.com".extraConfig = '' @assets path /cgit.css /cgit.js /favicon.svg /robots.txt handle @assets { - root * ${custom_cgit} - file_server + root * ${custom_cgit} + file_server } - reverse_proxy unix//run/fcgiwrap-cgit.sock { - transport fastcgi { - env SCRIPT_FILENAME ${custom_cgit}/cgit.cgi - } + reverse_proxy unix//run/cgit/fcgiwrap.sock { + transport fastcgi { + env SCRIPT_FILENAME ${custom_cgit}/cgit.cgi + } } ''; diff --git a/modules/services/nixos/default.nix b/modules/services/nixos/default.nix index 9aea299..44609a7 100644 --- a/modules/services/nixos/default.nix +++ b/modules/services/nixos/default.nix @@ -7,14 +7,11 @@ ./btopweb.nix ./cgit ./ganyupload - ./jta ./glance.nix - ./filebrowser.nix + ./dufs.nix ./minecraft.nix ./ngircd.nix ./qbittorrent.nix ]; - - users.groups."fileserver" = {}; } diff --git a/modules/services/nixos/dufs.nix b/modules/services/nixos/dufs.nix new file mode 100644 index 0000000..966d973 --- /dev/null +++ b/modules/services/nixos/dufs.nix @@ -0,0 +1,73 @@ +{ + lib, + pkgs, + config, + ... +}: let + cfg = config.collinux.services.dufs; + + modularService = { + lib, + config, + options, + ... + }: let + cfg = config.dufs; + in { + options.dufs = { + package = lib.mkOption { + description = "the `dufs` package"; + type = lib.types.package; + }; + dir = lib.mkOption { + description = "root directory of the fileserver"; + type = lib.types.str; + }; + }; + config = + {process.argv = [(lib.getExe cfg.package) cfg.dir "--bind" "/run/dufs/dufs.sock"];} + // lib.optionalAttrs (options ? systemd) { + systemd.service = { + wants = ["network-online.target"]; + after = ["network-online.target"]; + wantedBy = ["multi-user.target"]; + + serviceConfig = { + DynamicUser = true; + Group = "caddy"; + SupplementaryGroups = "fileserver"; + + RuntimeDirectory = "dufs"; + RuntimeDirectoryMode = "0750"; + UMask = "0002"; + + ProtectSystem = "strict"; + ProtectHome = true; + PrivateTmp = true; + NoNewPrivileges = true; + }; + }; + }; + }; +in + lib.mkIf cfg.enable { + users.groups."fileserver" = {}; + + system.services."dufs" = { + imports = [modularService]; + dufs = { + package = pkgs.dufs; + dir = "/media"; + }; + }; + + services.caddy.virtualHosts."files.ganymede".extraConfig = '' + tls internal + reverse_proxy unix//run/dufs/dufs.sock + ''; + + collinux.services.glance.homelabServices."files" = { + url = "https://files.ganymede"; + icon = "mdi:folder"; + }; + } diff --git a/modules/services/nixos/filebrowser.nix b/modules/services/nixos/filebrowser.nix deleted file mode 100644 index 9791f8d..0000000 --- a/modules/services/nixos/filebrowser.nix +++ /dev/null @@ -1,52 +0,0 @@ -{ - lib, - pkgs, - config, - ... -}: let - cfg = config.collinux.services.filebrowser; -in - lib.mkIf cfg.enable { - users.groups."dufs" = {}; - users.users."dufs" = { - isSystemUser = true; - group = "dufs"; - extraGroups = ["fileserver"]; - }; - users.users.caddy.extraGroups = ["dufs"]; - - systemd.services."dufs" = { - description = "dufs file server"; - restartIfChanged = true; - wants = ["network-online.target"]; - after = ["network-online.target"]; - wantedBy = ["multi-user.target"]; - - serviceConfig = { - ExecStart = "${lib.getExe pkgs.dufs} /media --bind /run/dufs/dufs.sock"; - - RuntimeDirectory = "dufs"; - - User = "dufs"; - Group = "dufs"; - UMask = "0007"; - - ProtectSystem = "strict"; - ProtectHome = true; - PrivateTmp = true; - NoNewPrivileges = true; - - Restart = "on-failure"; - }; - }; - - services.caddy.virtualHosts."files.ganymede".extraConfig = '' - tls internal - reverse_proxy unix//run/dufs/dufs.sock - ''; - - collinux.services.glance.homelabServices."files" = { - url = "https://files.ganymede"; - icon = "mdi:folder"; - }; - } diff --git a/modules/services/nixos/glance.nix b/modules/services/nixos/glance.nix index 8d305a5..2b7ba0b 100644 --- a/modules/services/nixos/glance.nix +++ b/modules/services/nixos/glance.nix @@ -6,111 +6,158 @@ }: let cfg = config.collinux.services.glance; - servicesLinks = builtins.attrValues cfg.homelabServices; - - settings = { - server = { - inherit (cfg) port; - proxied = true; - host = "127.0.0.1"; - assets-path = "/var/lib/glance"; + modularService = { + lib, + config, + options, + ... + }: let + cfg = config.glance; + in { + options.glance = { + package = lib.mkOption { + description = "the `glance` package"; + type = lib.types.package; + }; + settings = lib.mkOption { + description = "glance configuration file contents"; + type = (pkgs.formats.json {}).type; + }; }; - - branding.hide-footer = true; - pages = [ + config = { - name = "Dashboard"; - width = "slim"; - hide-desktop-navigation = true; - center-vertically = true; - columns = [ + configData."config.yml".text = builtins.toJSON ( { - size = "full"; - widgets = [ - { - type = "search"; - autofocus = true; - search-engine = "duckduckgo"; - bangs = [ - { - title = "GitHub"; - shortcut = "gh"; - url = "https://github.com/search?q={QUERY}&type=repositories"; - } - { - title = "I'm Feeling Lucky"; - shortcut = "!"; - url = "https://www.google.com/search?q={QUERY}&btnI=&sourceid=navclient&gfns=1"; - } - { - title = "YouTube Music"; - shortcut = "ytm"; - url = "https://music.youtube.com/search?q={QUERY}"; - } - { - title = "Google AI Mode"; - shortcut = "ai"; - url = "https://www.google.com/search?udm=50&q={QUERY}"; - } - ]; - } - { - type = "server-stats"; - servers = [ - { - type = "local"; - name = "ganymede"; - hide-mountpoints-by-default = true; - mountpoints = { - "/".hide = false; - "/media".hide = false; - }; - } - ]; - } - { - type = "monitor"; - cache = "1m"; - title = "Services"; - sites = servicesLinks; - } - ]; + server = { + port = 8080; + proxied = true; + host = "127.0.0.1"; + assets-path = "/var/lib/glance"; + }; } - ]; + // cfg.settings + ); + process.argv = [(lib.getExe cfg.package) "-config" config.configData."config.yml".path]; } - ]; - }; + // lib.optionalAttrs (options ? systemd) { + systemd.service = { + wantedBy = ["multi-user.target"]; - settingsFile = (pkgs.formats.yaml {}).generate "config.yml" settings; -in { - config = lib.mkIf cfg.enable { - systemd.services."glance" = { - restartIfChanged = true; - wants = ["network-online.target"]; - after = ["network-online.target"]; - wantedBy = ["multi-user.target"]; + serviceConfig = { + DynamicUser = true; + StateDirectory = "glance"; - serviceConfig = { - Type = "simple"; + PrivateNetwork = true; + NoNewPrivileges = true; + PrivateTmp = true; + ProtectSystem = "strict"; + ProtectHome = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + }; + }; - DynamicUser = true; - StateDirectory = "glance"; + systemd.sockets."glance-proxy" = { + wantedBy = ["sockets.target"]; + listenStreams = ["/run/glance/glance.sock"]; + socketConfig = { + SocketMode = "0660"; + SocketGroup = "caddy"; + }; + }; - ExecStart = "${lib.getExe pkgs.glance} -config ${settingsFile}"; + systemd.services."glance-proxy" = { + requires = ["glance.service"]; + after = ["glance.service"]; + unitConfig.JoinsNamespaceOf = "glance.service"; - NoNewPrivileges = true; - PrivateTmp = true; - ProtectSystem = "strict"; - ProtectHome = true; - ProtectKernelTunables = true; - ProtectKernelModules = true; - ProtectControlGroups = true; + serviceConfig = { + ExecStart = "${pkgs.systemd}/lib/systemd/systemd-socket-proxyd 127.0.0.1:8080"; + DynamicUser = true; + PrivateNetwork = true; + PrivateTmp = true; + NoNewPrivileges = true; + ProtectSystem = "strict"; + ProtectHome = true; + }; + }; + }; + }; +in { + config = lib.mkIf cfg.enable { + system.services."glance" = { + imports = [modularService]; + glance = { + package = pkgs.glance; + settings = let + servicesLinks = builtins.attrValues cfg.homelabServices; + wrap = x: [x]; + in { + branding.hide-footer = true; + pages = wrap { + name = "Dashboard"; + width = "slim"; + hide-desktop-navigation = true; + center-vertically = true; + columns = wrap { + size = "full"; + widgets = [ + { + type = "search"; + autofocus = true; + search-engine = "duckduckgo"; + bangs = [ + { + title = "GitHub"; + shortcut = "gh"; + url = "https://github.com/search?q={QUERY}&type=repositories"; + } + { + title = "I'm Feeling Lucky"; + shortcut = "!"; + url = "https://www.google.com/search?q={QUERY}&btnI=&sourceid=navclient&gfns=1"; + } + { + title = "YouTube Music"; + shortcut = "ytm"; + url = "https://music.youtube.com/search?q={QUERY}"; + } + { + title = "Google AI Mode"; + shortcut = "ai"; + url = "https://www.google.com/search?udm=50&q={QUERY}"; + } + ]; + } + { + type = "server-stats"; + servers = wrap { + type = "local"; + name = "ganymede"; + hide-mountpoints-by-default = true; + mountpoints = { + "/".hide = false; + "/media".hide = false; + }; + }; + } + { + type = "monitor"; + cache = "1m"; + title = "Services"; + sites = servicesLinks; + } + ]; + }; + }; + }; }; }; services.caddy.virtualHosts."home.ganymede".extraConfig = '' tls internal - reverse_proxy 127.0.0.1:${toString cfg.port} + reverse_proxy unix///run/glance/glance.sock ''; }; } diff --git a/modules/services/nixos/goaccess.nix b/modules/services/nixos/goaccess.nix index f0b6c34..15a3768 100644 --- a/modules/services/nixos/goaccess.nix +++ b/modules/services/nixos/goaccess.nix @@ -7,78 +7,107 @@ }: let cfg = config.collinux.services.goaccess; - settings = { - date-format = "%s"; - log-format = "CADDY"; - tz = config.time.timeZone; - log-file = "/var/log/caddy/access-williamsfam.us.com.log"; - geoip-database = inputs.geolite-db; + modularService = { + lib, + config, + options, + ... + }: let + cfg = config.goaccess; - ws-url = "wss://stats.ganymede:443/ws"; # url that the frontend uses to fetch data - unix-socket = "/run/goaccess/goaccess.sock"; + settings = + cfg.settings + // { + unix-socket = "/run/goaccess/goaccess.sock"; + output = "/run/goaccess/www/index.html"; + }; + in { + options.goaccess = { + package = lib.mkOption { + description = "the `goaccess` package"; + type = lib.types.package; + }; + settings = lib.mkOption { + description = "goaccess.conf options"; + type = lib.types.attrsOf lib.types.str; + }; + }; - real-time-html = "true"; - output = "/var/lib/goaccess/index.html"; - external-assets = "true"; - all-static-files = "false"; - html-report-title = "stats@ganymede"; - hl-header = "true"; - agent-list = "false"; - with-output-resolver = "false"; - http-method = "yes"; - http-protocol = "yes"; - "4xx-to-unique-count" = "false"; - ignore-crawlers = "false"; - crawlers-only = "false"; - unknowns-as-crawlers = "false"; - real-os = "true"; - }; + config = + { + configData."goaccess.conf".text = + settings + |> lib.mapAttrsToList (k: v: "${k} ${v}") + |> lib.concatStringsSep "\n"; - settingsFile = pkgs.writeText "goaccess.conf" (settings |> builtins.mapAttrs (k: v: "${k} ${toString v}") |> builtins.attrValues |> lib.concatStringsSep "\n"); -in { - config = lib.mkIf cfg.enable { - users.groups."goaccess" = {}; - users.users."goaccess" = { - isSystemUser = true; - group = "goaccess"; - extraGroups = ["caddy"]; # to read caddy log files - }; - users.users.caddy.extraGroups = ["goaccess"]; + process.argv = [(lib.getExe cfg.package) "-p" config.configData."goaccess.conf".path]; + } + // lib.optionalAttrs (options ? systemd) { + systemd.service = { + description = "GoAccess Real-Time Log Analyzer"; + restartIfChanged = true; + wants = ["network-online.target" "caddy.service"]; + after = ["network-online.target" "caddy.service"]; + wantedBy = ["multi-user.target"]; - systemd.services."goaccess" = { - description = "GoAccess Real-Time Log Analyzer"; - restartIfChanged = true; - wants = ["network-online.target" "caddy.service"]; - after = ["network-online.target" "caddy.service"]; - wantedBy = ["multi-user.target"]; + serviceConfig = { + DynamicUser = true; + Group = "caddy"; - serviceConfig = { - User = "goaccess"; - Type = "simple"; + RuntimeDirectory = ["goaccess" "goaccess/www"]; + RuntimeDirectoryMode = "0750"; + UMask = "0007"; # socket -> 0770, files -> 0660 - RuntimeDirectory = "goaccess"; - RuntimeDirectoryMode = "0770"; - UMask = "0007"; + NoNewPrivileges = true; + PrivateTmp = true; + ProtectSystem = "strict"; + ProtectHome = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; - StateDirectory = "goaccess"; - StateDirectoryMode = "0750"; # caddy must read this dir + Restart = "on-failure"; + }; + }; + }; + }; +in { + config = lib.mkIf cfg.enable { + system.services."goaccess" = { + imports = [modularService]; + goaccess = { + package = pkgs.goaccess; + settings = { + date-format = "%s"; + log-format = "CADDY"; + tz = config.time.timeZone; + log-file = "/var/log/caddy/access-williamsfam.us.com.log"; + geoip-database = toString inputs.geolite-db; - ExecStart = "${lib.getExe pkgs.goaccess} -p ${settingsFile}"; + ws-url = "wss://stats.ganymede:443/ws"; # url that the frontend uses to fetch data - NoNewPrivileges = true; - PrivateTmp = true; - ProtectSystem = "strict"; - ProtectHome = true; - ProtectKernelTunables = true; - ProtectKernelModules = true; - ProtectControlGroups = true; + real-time-html = "true"; + external-assets = "true"; + all-static-files = "false"; + html-report-title = "stats@ganymede"; + hl-header = "true"; + agent-list = "false"; + with-output-resolver = "false"; + http-method = "yes"; + http-protocol = "yes"; + "4xx-to-unique-count" = "false"; + ignore-crawlers = "false"; + crawlers-only = "false"; + unknowns-as-crawlers = "false"; + real-os = "true"; + }; }; }; services.caddy.virtualHosts."stats.ganymede".extraConfig = '' tls internal - root * /var/lib/goaccess + root * /run/goaccess/www file_server reverse_proxy /ws unix//run/goaccess/goaccess.sock diff --git a/modules/services/nixos/jta/assignments.html b/modules/services/nixos/jta/assignments.html deleted file mode 100644 index 3031a29..0000000 --- a/modules/services/nixos/jta/assignments.html +++ /dev/null @@ -1,148 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> - <head> - <meta charset="UTF-8"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>jta@ganymede</title> - <link rel="stylesheet" href="style.css"> - <style> - body { - margin: 0; - max-width: none; - padding: 0; - background: var(--bg); - } - - header { - padding: 1.25rem 1.5rem; - text-align: center; - font-family: var(--font-heading); - font-size: var(--step-2); - line-height: 1.2; - letter-spacing: -0.01em; - font-weight: 700; - background: #020617; - border-bottom: 1px solid var(--border); - } - - .section { - padding: 1rem; - max-width: 1100px; - margin: 0 auto; - } - - .section-title { - font-size: var(--step-1); - line-height: 1.25; - letter-spacing: -0.01em; - margin: 1rem 0 0.5rem 0.5rem; - color: var(--text-soft); - } - - .container { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); - gap: 1rem; - padding: 0 1rem 1rem 1rem; - } - - .card { - background: var(--card); - border: 1px solid var(--border); - border-radius: 16px; - padding: 1rem; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); - transition: transform 0.15s ease, box-shadow 0.15s ease; - cursor: pointer; - text-decoration: none; - color: var(--text); - display: block; - } - - .card:hover { - transform: translateY(-4px); - box-shadow: 0 10px 20px rgba(0, 0, 0, 0.6); - } - - .title { - font-size: var(--step-1); - line-height: 1.3; - letter-spacing: -0.01em; - font-weight: 600; - margin-bottom: 0.5rem; - } - - .due { - font-size: var(--step--1); - color: var(--muted); - margin-bottom: 0.5rem; - } - - .desc { - font-size: var(--step--1); - color: var(--text-soft); - line-height: 1.55; - } - </style> - </head> - <body> - <header>Juksere Trives Aldri</header> - - <div id="app"></div> - - <script> - const app = document.getElementById("app"); - - function renderHome(data) { - const groups = {}; - data.forEach((item) => { - const cls = item.class || "Other"; - if (!groups[cls]) groups[cls] = []; - groups[cls].push(item); - }); - - Object.entries(groups).forEach(([cls, items]) => { - const section = document.createElement("div"); - section.className = "section"; - - const title = document.createElement("div"); - title.className = "section-title"; - title.textContent = cls; - section.appendChild(title); - - const container = document.createElement("div"); - container.className = "container"; - - items.forEach((item) => { - const card = document.createElement("a"); - card.className = "card"; - card.href = item.link || "#"; - - card.innerHTML = ` - <div class="title">${item.title}</div> - <div class="due">Due: ${item.due}</div> - ${item.description ? `<div class="desc">${item.description}</div>` : ""} - `; - - container.appendChild(card); - }); - - section.appendChild(container); - app.appendChild(section); - }); - } - - fetch("assignments.json") - .then((res) => { - if (!res.ok) { - throw new Error("Failed to load data"); - } - return res.json(); - }) - .then((data) => renderHome(data)) - .catch(() => { - app.innerHTML = "<p style='padding:1rem;'>Failed to load data.</p>"; - }); - </script> - </body> -</html> diff --git a/modules/services/nixos/jta/default.nix b/modules/services/nixos/jta/default.nix deleted file mode 100644 index 31f5bbd..0000000 --- a/modules/services/nixos/jta/default.nix +++ /dev/null @@ -1,48 +0,0 @@ -{ - pkgs, - config, - lib, - ... -}: let - cfg = config.collinux.services.jta; - package = pkgs.callPackage ./pkg.nix {}; -in { - config = lib.mkIf cfg.enable { - users.groups."jta" = {}; - users.users."jta" = { - isSystemUser = true; - group = "jta"; - extraGroups = ["fileserver"]; - - home = "/var/lib/jta"; - createHome = true; - homeMode = "755"; - }; - - systemd.services."jta" = { - restartIfChanged = true; - wants = ["network-online.target" "caddy.service"]; - after = ["network-online.target" "caddy.service"]; - - environment = { - PORT = toString cfg.port; - ROOT_DIR = "/media/jta"; - AUTH_PASSWORD_HASH = "550b6785c4bce2ab41a5e2eaa1eb43172d5abbd58237f1c78cd32cb190edc2c5"; # alfreddabuttlerwithtwots - }; - - serviceConfig = { - User = "jta"; - Type = "simple"; - - WorkingDirectory = "/var/lib/jta"; - ExecStart = "${package}/bin/jta"; - }; - - wantedBy = ["multi-user.target"]; - }; - - services.caddy.virtualHosts."jta.williamsfam.us.com".extraConfig = '' - reverse_proxy 127.0.0.1:${toString cfg.port} - ''; - }; -} diff --git a/modules/services/nixos/jta/go.mod b/modules/services/nixos/jta/go.mod deleted file mode 100644 index 8f27af1..0000000 --- a/modules/services/nixos/jta/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module git.ganymede/jta - -go 1.25.5 - -require github.com/yuin/goldmark v1.8.2 diff --git a/modules/services/nixos/jta/go.sum b/modules/services/nixos/jta/go.sum deleted file mode 100644 index 6a37955..0000000 --- a/modules/services/nixos/jta/go.sum +++ /dev/null @@ -1,2 +0,0 @@ -github.com/yuin/goldmark v1.8.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE= -github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= diff --git a/modules/services/nixos/jta/index.html b/modules/services/nixos/jta/index.html deleted file mode 100644 index 96b6f5f..0000000 --- a/modules/services/nixos/jta/index.html +++ /dev/null @@ -1,134 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> - <head> - <meta charset="UTF-8"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>JTA Login</title> - <link rel="stylesheet" href="style.css"> - <style> - body { - margin: 0; - min-height: 100vh; - display: grid; - place-items: center; - padding: 1rem; - background: var(--bg); - } - - .card { - width: min(520px, 100%); - background: var(--card); - border: 1px solid var(--border); - border-radius: 16px; - padding: 1.5rem; - box-shadow: 0 10px 28px rgba(0, 0, 0, 0.38); - } - - h1 { - margin: 0 0 0.25rem 0; - text-align: center; - font-family: var(--font-heading); - font-size: var(--step-2); - line-height: 1.15; - font-weight: 700; - } - - p { - margin: 0.5rem 0 1.2rem 0; - text-align: center; - color: var(--text-soft); - line-height: 1.5; - } - - .actions { - display: flex; - justify-content: center; - gap: 0.75rem; - } - - button { - appearance: none; - border: 1px solid var(--border); - border-radius: 12px; - background: #0f172a; - color: var(--text); - padding: 0.65rem 1rem; - font-size: var(--step-0); - font-weight: 600; - cursor: pointer; - transition: transform 0.15s ease, box-shadow 0.15s ease; - } - - button:hover { - transform: translateY(-1px); - box-shadow: 0 6px 16px rgba(0, 0, 0, 0.35); - } - - #status { - margin-top: 1rem; - text-align: center; - min-height: 1.25rem; - font-size: var(--step--1); - color: var(--text-soft); - } - </style> - </head> - <body> - <div class="card"> - <h1>JTA Portal</h1> - <p>Public landing page. Login is required to view assignments and all other pages.</p> - - <div class="actions"> - <button id="login-btn" type="button">Login</button> - </div> - - <div id="status" aria-live="polite"></div> - </div> - - <script> - const authCookieName = "jta_auth"; - const status = document.getElementById("status"); - const loginButton = document.getElementById("login-btn"); - - function toHex(buffer) { - const bytes = new Uint8Array(buffer); - return Array.from(bytes) - .map((b) => b.toString(16).padStart(2, "0")) - .join(""); - } - - async function sha256Hex(input) { - const data = new TextEncoder().encode(input); - const digest = await crypto.subtle.digest("SHA-256", data); - return toHex(digest); - } - - function setAuthCookie(hashValue) { - const maxAge = 10 * 365 * 24 * 60 * 60; - document.cookie = `${authCookieName}=${hashValue}; Path=/; Max-Age=${maxAge}; SameSite=Lax`; - } - - async function loginWithPrompt() { - const password = prompt("Enter password"); - if (password === null) { - status.textContent = "Login cancelled."; - return; - } - - status.textContent = "Logging in..."; - - const hashValue = await sha256Hex(password); - setAuthCookie(hashValue); - - status.textContent = "Success. Redirecting..."; - window.location.href = "/assignments"; - } - - loginButton.addEventListener("click", () => { - loginWithPrompt().catch(() => { - status.textContent = "Network error while logging in."; - }); - }); - </script> - </body> -</html> diff --git a/modules/services/nixos/jta/main.go b/modules/services/nixos/jta/main.go deleted file mode 100644 index e7b5a0f..0000000 --- a/modules/services/nixos/jta/main.go +++ /dev/null @@ -1,142 +0,0 @@ -package main - -import ( - "bytes" - _ "embed" - "html/template" - "net/http" - "os" - "path/filepath" - "strings" - - "github.com/yuin/goldmark" - "github.com/yuin/goldmark/extension" -) - -// Configure Goldmark with GitHub Flavored Markdown -var mdParser = goldmark.New( - goldmark.WithExtensions( - extension.GFM, - ), -) - -//go:embed md_template.html -var mdTemplate string - -//go:embed style.css -var styleCSS string - -//go:embed index.html -var indexHTML string - -//go:embed assignments.html -var assignmentsHTML string - -var pageTmpl = template.Must(template.New("md_template.html").Parse(mdTemplate)) - -const authCookieName = "jta_auth" - -func isAuthenticated(w http.ResponseWriter, r *http.Request) bool { - expectedHash := strings.TrimSpace(os.Getenv("AUTH_PASSWORD_HASH")) - if expectedHash == "" { - return false - } - - // Try cookie first - if cookie, err := r.Cookie(authCookieName); err == nil { - providedHash := strings.TrimSpace(cookie.Value) - if providedHash != "" && strings.EqualFold(providedHash, expectedHash) { - return true - } - } - - // Fallback to URL parameter - paramHash := strings.TrimSpace(r.URL.Query().Get(authCookieName)) - if paramHash != "" && strings.EqualFold(paramHash, expectedHash) { - http.SetCookie(w, &http.Cookie{ - Name: authCookieName, - Value: paramHash, - Path: "/", - HttpOnly: true, // Security: Protects against XSS cookie theft - Secure: false, // Set to true if you are hosting over HTTPS - SameSite: http.SameSiteLaxMode, // Prevents CSRF vulnerabilities - }) - return true - } - - return false -} - -func serveMarkdown(w http.ResponseWriter, r *http.Request) { - // Prevent path traversal - clean := filepath.Clean(r.URL.Path) - path := filepath.Join(rootDir, strings.TrimPrefix(clean, "/")) - - mdBytes, err := os.ReadFile(path) - if err != nil { - http.NotFound(w, r) - return - } - - var buf bytes.Buffer - if err := mdParser.Convert(mdBytes, &buf); err != nil { - http.Error(w, "failed to render markdown", 500) - return - } - - w.Header().Set("Content-Type", "text/html; charset=utf-8") - - pageTmpl.Execute(w, map[string]template.HTML{ - "Content": template.HTML(buf.String()), - }) -} - -var rootDir = "." - -func main() { - if envRootDir := os.Getenv("ROOT_DIR"); envRootDir != "" { - rootDir = envRootDir - } - - fs := http.FileServer(http.Dir(rootDir)) - port := os.Getenv("PORT") - if port == "" { - port = "8080" - } - - http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == "/" || r.URL.Path == "/index.html" { - w.Header().Set("Content-Type", "text/html; charset=utf-8") - w.Write([]byte(indexHTML)) - return - } - - if r.URL.Path == "/style.css" { - w.Header().Set("Content-Type", "text/css; charset=utf-8") - w.Write([]byte(styleCSS)) - return - } - - if !isAuthenticated(w, r) { - http.Error(w, "unauthorized", http.StatusUnauthorized) - return - } - - if r.URL.Path == "/assignments" || r.URL.Path == "/assignments.html" { - w.Header().Set("Content-Type", "text/html; charset=utf-8") - w.Write([]byte(assignmentsHTML)) - return - } - - // Intercept markdown files - if strings.HasSuffix(r.URL.Path, ".md") { - serveMarkdown(w, r) - return - } - - // Otherwise serve normally - fs.ServeHTTP(w, r) - }) - - http.ListenAndServe(":"+port, nil) -} diff --git a/modules/services/nixos/jta/md_template.html b/modules/services/nixos/jta/md_template.html deleted file mode 100644 index daa57b7..0000000 --- a/modules/services/nixos/jta/md_template.html +++ /dev/null @@ -1,11 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> - <head> - <meta charset="UTF-8"> - <title>JTA</title> - <link rel="stylesheet" href="/style.css"> - </head> - <body> - {{.Content}} - </body> -</html> diff --git a/modules/services/nixos/jta/pkg.nix b/modules/services/nixos/jta/pkg.nix deleted file mode 100644 index c8f7d77..0000000 --- a/modules/services/nixos/jta/pkg.nix +++ /dev/null @@ -1,6 +0,0 @@ -{pkgs ? import <nixpkgs> {system = "x86_64-linux";}, ...}: -pkgs.buildGoModule { - name = "jta"; - src = ./.; - vendorHash = "sha256-aeJYO52ALbGxyTFPw6eGCmPlmuCvbXEoX9gcXatN29E="; -} diff --git a/modules/services/nixos/jta/style.css b/modules/services/nixos/jta/style.css deleted file mode 100644 index ec09ca9..0000000 --- a/modules/services/nixos/jta/style.css +++ /dev/null @@ -1,172 +0,0 @@ -:root { - --bg: #0f172a; - --card: #1e293b; - --accent: #22c55e; - --text: #e2e8f0; - --text-soft: #cbd5e1; - --muted: #94a3b8; - --border: #334155; - - --font-body: "Avenir Next", "Segoe UI", "Noto Sans", "Helvetica Neue", sans-serif; - --font-heading: "Iowan Old Style", "Palatino Linotype", "Book Antiqua", Palatino, serif; - --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; - - --step--1: clamp(0.88rem, 0.86rem + 0.12vw, 0.96rem); - --step-0: clamp(1rem, 0.98rem + 0.16vw, 1.08rem); - --step-1: clamp(1.14rem, 1.08rem + 0.26vw, 1.28rem); - --step-2: clamp(1.3rem, 1.2rem + 0.45vw, 1.56rem); - --step-3: clamp(1.7rem, 1.45rem + 1.1vw, 2.25rem); -} - -/* Base layout */ -body { - font-family: var(--font-body); - font-size: var(--step-0); - background: var(--bg); - color: var(--text); - max-width: 850px; - margin: 2rem auto; - padding: 1.5rem; - line-height: 1.7; - text-rendering: optimizeLegibility; -} - -/* Headings */ -h1 { - font-family: var(--font-heading); - font-size: var(--step-3); - line-height: 1.15; - font-weight: 700; - letter-spacing: -0.02em; - text-align: center; - margin: 0 0 1.25rem; -} - -h2 { - font-family: var(--font-heading); - margin-top: 2.25rem; - margin-bottom: 0.75rem; - font-size: var(--step-2); - line-height: 1.25; - letter-spacing: -0.01em; -} - -h3 { - font-family: var(--font-heading); - margin-top: 1.5rem; - margin-bottom: 0.6rem; - font-size: var(--step-1); - line-height: 1.3; - color: var(--text-soft); -} - -/* Text */ -p { - color: var(--text); - margin-bottom: 1rem; - max-width: 65ch; -} - -/* Ordered list (questions) */ -ol { - padding-left: 1.2rem; - max-width: 65ch; -} - -/* Answer list */ -ul { - list-style: none; - padding-left: 1.2rem; - max-width: 65ch; -} - -ul li { - display: flex; - align-items: flex-start; - gap: 0.6rem; - padding: 0.35rem 0.4rem; - border-radius: 8px; - transition: background 0.15s ease; -} - -/* Checkbox styling */ -input[type="checkbox"] { - accent-color: var(--accent); - transform: scale(1.2); - margin-top: 0.2rem; - pointer-events: none; -} - -/* Correct answers */ -li:has(input[type="checkbox"]:checked) { - color: #86efac; - font-weight: 500; -} - -/* Code blocks */ -pre { - font-family: var(--font-mono); - font-size: var(--step--1); - line-height: 1.6; - background: #020617; - padding: 1rem; - border-radius: 10px; - overflow-x: auto; - margin: 1rem 0; - max-width: 65ch; -} - -code { - font-family: var(--font-mono); - font-size: 0.95em; - background: #020617; - padding: 2px 6px; - border-radius: 6px; -} - -img { - display: block; - max-width: 100%; - height: auto; - margin: 1rem 0; - border-radius: 10px; -} - -p > img, -li > img { - margin: 0.75rem 0; -} - -a > img { - transition: transform 0.15s ease, box-shadow 0.15s ease; -} - -a:hover > img { - transform: translateY(-1px); - box-shadow: 0 8px 20px rgba(2, 6, 23, 0.45); -} - -img[width], -img[height] { - width: auto; - height: auto; - max-width: 100%; -} - -/* Links */ -a { - color: #93c5fd; - text-decoration-line: underline; - text-decoration-thickness: 2px; - text-underline-offset: 0.14em; - text-decoration-color: rgba(147, 197, 253, 0.45); -} - -a:hover { - text-decoration-color: currentColor; -} - -/* Optional: make everything feel tighter and nicer */ -* { - box-sizing: border-box; -} diff --git a/modules/services/nixos/minecraft.nix b/modules/services/nixos/minecraft.nix index 0209549..9e83c7e 100644 --- a/modules/services/nixos/minecraft.nix +++ b/modules/services/nixos/minecraft.nix @@ -15,7 +15,7 @@ in SERVER_NAME = "YServer"; TZ = config.time.timeZone; - VERSION = "1.26.31.1"; + VERSION = "1.26.52.3"; CONTENT_LOG_FILE_ENABLED = "false"; ALLOW_CHEATS = "false"; diff --git a/modules/services/nixos/qbittorrent.nix b/modules/services/nixos/qbittorrent.nix index c7bfe95..eb0e8ad 100644 --- a/modules/services/nixos/qbittorrent.nix +++ b/modules/services/nixos/qbittorrent.nix @@ -6,8 +6,6 @@ cfg = config.collinux.services.qbittorrent; in { config = lib.mkIf cfg.enable { - users.users."qbittorrent" = {}; - networking.firewall = { allowedTCPPorts = [49252]; allowedUDPPorts = [49252]; @@ -18,7 +16,6 @@ in { webuiPort = cfg.port; torrentingPort = 49252; }; - systemd.services.qbittorrent.serviceConfig.PrivateUsers = lib.mkForce false; services.caddy.virtualHosts."bittorrent.ganymede".extraConfig = '' tls internal diff --git a/modules/services/options.nix b/modules/services/options.nix index 38baf9e..a066836 100644 --- a/modules/services/options.nix +++ b/modules/services/options.nix @@ -24,31 +24,30 @@ in { forgejo = basicService {desc = "Self-hosted git forge";}; qbittorrent = basicService {desc = "webui for qBittorrent";}; + dufs.enable = mkEnableOption "dufs file browser"; goaccess.enable = mkEnableOption "webserver stats from caddy logs"; - filebrowser.enable = mkEnableOption "dufs file browser"; cgit.enable = mkEnableOption "cgit git webui"; - glance = - (basicService {desc = "Glance homepage";}) - // { - homelabServices = lib.mkOption { - type = lib.types.attrsOf (lib.types.submodule ({config, ...}: { - options = { - title = lib.mkOption { - type = lib.types.str; - default = config._module.args.name; - }; - url = lib.mkOption { - type = lib.types.str; - }; - icon = lib.mkOption { - type = lib.types.nullOr lib.types.str; - default = null; - }; + glance = { + enable = mkEnableOption "glance homelab homepage"; + homelabServices = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule ({config, ...}: { + options = { + title = lib.mkOption { + type = lib.types.str; + default = config._module.args.name; }; - })); - }; + url = lib.mkOption { + type = lib.types.str; + }; + icon = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + }; + }; + })); }; + }; minecraft = basicService { desc = "Minecraft bedrock server"; |
