aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCollin Williams <96917990+bluedragon1221@users.noreply.github.com>2026-10-02 11:23:45 -0500
committerCollin Williams <96917990+bluedragon1221@users.noreply.github.com>2026-10-02 11:23:45 -0500
commitb768ad0ca4208e8b3588b881067c43d85246e967 (patch)
tree460dca5c6568307efc94e256186346be0271478a
parent84364a52c3d7ca2f550cb3f7af56848405902bee (diff)
experiment with modular services; more unix domain sockets, clean up old code
-rw-r--r--flake.lock32
-rw-r--r--hosts/ganymede/caddy.nix2
-rw-r--r--hosts/ganymede/config.nix7
-rw-r--r--hosts/ganymede/networking.nix13
-rw-r--r--hosts/mercury/hjem.nix47
-rw-r--r--hosts/mercury/networking.nix16
-rw-r--r--hosts/mercury/nixos.nix15
-rw-r--r--modules/services/nixos/btopweb.nix113
-rw-r--r--modules/services/nixos/cgit/default.nix36
-rw-r--r--modules/services/nixos/default.nix5
-rw-r--r--modules/services/nixos/dufs.nix73
-rw-r--r--modules/services/nixos/filebrowser.nix52
-rw-r--r--modules/services/nixos/glance.nix227
-rw-r--r--modules/services/nixos/goaccess.nix143
-rw-r--r--modules/services/nixos/jta/assignments.html148
-rw-r--r--modules/services/nixos/jta/default.nix48
-rw-r--r--modules/services/nixos/jta/go.mod5
-rw-r--r--modules/services/nixos/jta/go.sum2
-rw-r--r--modules/services/nixos/jta/index.html134
-rw-r--r--modules/services/nixos/jta/main.go142
-rw-r--r--modules/services/nixos/jta/md_template.html11
-rw-r--r--modules/services/nixos/jta/pkg.nix6
-rw-r--r--modules/services/nixos/jta/style.css172
-rw-r--r--modules/services/nixos/minecraft.nix2
-rw-r--r--modules/services/nixos/qbittorrent.nix3
-rw-r--r--modules/services/options.nix39
26 files changed, 478 insertions, 1015 deletions
diff --git a/flake.lock b/flake.lock
index 9de70cd..a034f4b 100644
--- a/flake.lock
+++ b/flake.lock
@@ -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";