aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCollin Williams <96917990+bluedragon1221@users.noreply.github.com>2026-10-02 21:24:08 -0500
committerCollin Williams <96917990+bluedragon1221@users.noreply.github.com>2026-10-02 21:24:08 -0500
commitf8c75486dbbf188ebbe83aefd582081739d97a86 (patch)
treeb55233284a350ef34f19b24a4e2142cefccd9c75
parent4300047e5fe88a9f7aad095ac500af3792d3855f (diff)
clean up more code
-rw-r--r--flake.lock4
-rw-r--r--flake.nix2
-rw-r--r--hosts/ganymede/caddy.nix7
-rw-r--r--hosts/ganymede/networking.nix18
-rw-r--r--hosts/mercury/nixos.nix5
-rw-r--r--hosts/mercury/secrets/ts-key.agebin274 -> 0 bytes
-rw-r--r--hosts/mercury/secrets/wireguard-privkey.age6
-rw-r--r--modules/nix/nixos/default.nix2
-rw-r--r--modules/services/nixos/btopweb.nix2
-rw-r--r--modules/services/nixos/caddy.nix7
-rw-r--r--modules/services/nixos/default.nix1
-rw-r--r--modules/services/nixos/dufs.nix2
-rw-r--r--modules/services/nixos/ganyupload/README13
-rw-r--r--modules/services/nixos/ganyupload/default.nix46
-rw-r--r--modules/services/nixos/ganyupload/go.mod3
-rw-r--r--modules/services/nixos/ganyupload/main.go302
-rw-r--r--modules/services/nixos/ganyupload/pkg.nix6
-rw-r--r--modules/services/nixos/glance.nix2
-rw-r--r--modules/services/nixos/goaccess.nix2
-rw-r--r--modules/services/nixos/openssh.nix1
-rw-r--r--modules/services/nixos/qbittorrent.nix2
-rw-r--r--modules/services/options.nix3
-rw-r--r--modules/system/nixos/boot.nix7
-rw-r--r--modules/user/nixos/default.nix2
24 files changed, 38 insertions, 407 deletions
diff --git a/flake.lock b/flake.lock
index e2f9ba7..92dc159 100644
--- a/flake.lock
+++ b/flake.lock
@@ -393,11 +393,11 @@
"rev": "03f93890672d169de4f77f0149b3a39c30be2ec9",
"revCount": 3,
"type": "git",
- "url": "ssh://git@ganymede/~/shared-assets"
+ "url": "https://git.collin.williamsfam.us.com/shared-assets"
},
"original": {
"type": "git",
- "url": "ssh://git@ganymede/~/shared-assets"
+ "url": "https://git.collin.williamsfam.us.com/shared-assets"
}
},
"systems": {
diff --git a/flake.nix b/flake.nix
index 80a56db..17c6eb4 100644
--- a/flake.nix
+++ b/flake.nix
@@ -39,7 +39,7 @@
};
shared-assets = {
- url = "git+ssh://git@ganymede/~/shared-assets";
+ url = "git+https://git.collin.williamsfam.us.com/shared-assets";
flake = false;
};
diff --git a/hosts/ganymede/caddy.nix b/hosts/ganymede/caddy.nix
index b7a4cef..42935cf 100644
--- a/hosts/ganymede/caddy.nix
+++ b/hosts/ganymede/caddy.nix
@@ -9,11 +9,8 @@
services.caddy = {
package = lib.mkForce (pkgs.caddy.withPlugins {
- plugins = [
- "github.com/tailscale/caddy-tailscale@v0.0.0-20251204171825-f070d146dd61"
- "github.com/caddy-dns/porkbun@v0.3.1"
- ];
- hash = "sha256-3BRyQ/fqPUemW1KqwyvkO1LeZB7PyBMIL/5a2u1mqqU=";
+ plugins = ["github.com/caddy-dns/porkbun@v0.3.1"];
+ hash = "sha256-CjL8dMdnsiawaPiQGRvL3he4Ydd3nIbQs6tBWMwUbaw=";
});
globalConfig = ''
diff --git a/hosts/ganymede/networking.nix b/hosts/ganymede/networking.nix
index 1ec83b2..a8ce2d1 100644
--- a/hosts/ganymede/networking.nix
+++ b/hosts/ganymede/networking.nix
@@ -32,11 +32,19 @@ in {
};
};
systemd = {
- services."disable-wifi-powersave" = {
- description = "Disable wifi powersaving using iw";
- after = ["network.target"];
- serviceConfig.ExecStart = "${lib.getExe pkgs.iw} dev wlp108s0 set power_save off";
- wantedBy = ["default.target"];
+ services."disable-wifi-powersave" = let
+ interface = "wlp108s0";
+ dev = "sys-subsystem-net-devices-${interface}.device";
+ in {
+ bindsTo = [dev];
+ after = [dev];
+ wantedBy = [dev];
+
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ ExecStart = "${lib.getExe pkgs.iw} dev ${interface} set power_save off";
+ };
};
network = {
diff --git a/hosts/mercury/nixos.nix b/hosts/mercury/nixos.nix
index 4eab7b4..f0d9af6 100644
--- a/hosts/mercury/nixos.nix
+++ b/hosts/mercury/nixos.nix
@@ -42,6 +42,11 @@
hosts.ganymede.caddy-root-ca
];
+ services.journald.extraConfig = ''
+ Storage=volatile
+ RuntimeMaxUse=100M
+ '';
+
# get perl out of my closure
system = {
etc.overlay.enable = true;
diff --git a/hosts/mercury/secrets/ts-key.age b/hosts/mercury/secrets/ts-key.age
deleted file mode 100644
index 57454fe..0000000
--- a/hosts/mercury/secrets/ts-key.age
+++ /dev/null
Binary files differ
diff --git a/hosts/mercury/secrets/wireguard-privkey.age b/hosts/mercury/secrets/wireguard-privkey.age
deleted file mode 100644
index 614778d..0000000
--- a/hosts/mercury/secrets/wireguard-privkey.age
+++ /dev/null
@@ -1,6 +0,0 @@
-age-encryption.org/v1
--> ssh-ed25519 azBilg GS1BNOAVj8aZPePeR4teX45zKAyCMVOBvE+GNUGCX3I
-rEzBojvbakMsQCKXROwbynFTxmlcPROD7DBsl/wzfO4
---- ESZvgbmZ3UrE6CymN/Haej2rE+FrS13SCqZZX+gcsPw
-ö 6T݈²ð—%]’¬‚ÓÔøK2®¥›ê
- Jï-íì{‡â;@öØ `á+E2¦ðÏGÛLÛF*Þ-Ña$ˆÒ`géœý�AÝm™pü \ No newline at end of file
diff --git a/modules/nix/nixos/default.nix b/modules/nix/nixos/default.nix
index 64f8c52..194c5f6 100644
--- a/modules/nix/nixos/default.nix
+++ b/modules/nix/nixos/default.nix
@@ -10,8 +10,6 @@
];
nix = {
- gc.automatic = false; # use nh cleaner instead
-
# Make builds run with low priority so my system stays responsive
daemonCPUSchedPolicy = "idle";
daemonIOSchedClass = "idle";
diff --git a/modules/services/nixos/btopweb.nix b/modules/services/nixos/btopweb.nix
index 74a2447..812e97e 100644
--- a/modules/services/nixos/btopweb.nix
+++ b/modules/services/nixos/btopweb.nix
@@ -94,7 +94,7 @@ in {
};
services.caddy.virtualHosts."btop.ganymede".extraConfig = ''
- tls internal
+ import internal
reverse_proxy unix//run/btopweb/ttyd.sock
'';
diff --git a/modules/services/nixos/caddy.nix b/modules/services/nixos/caddy.nix
index 02aeca3..77a1891 100644
--- a/modules/services/nixos/caddy.nix
+++ b/modules/services/nixos/caddy.nix
@@ -13,5 +13,12 @@ in
services.caddy = {
enable = true;
environmentFile = cfg.envFile;
+ extraConfig = ''
+ (internal) {
+ @outside not remote_ip 10.100.0.0/24 192.168.50.0/24
+ abort @outside
+ tls internal
+ }
+ '';
};
}
diff --git a/modules/services/nixos/default.nix b/modules/services/nixos/default.nix
index 44609a7..614cbfb 100644
--- a/modules/services/nixos/default.nix
+++ b/modules/services/nixos/default.nix
@@ -6,7 +6,6 @@
./goaccess.nix
./btopweb.nix
./cgit
- ./ganyupload
./glance.nix
./dufs.nix
diff --git a/modules/services/nixos/dufs.nix b/modules/services/nixos/dufs.nix
index 966d973..22068bf 100644
--- a/modules/services/nixos/dufs.nix
+++ b/modules/services/nixos/dufs.nix
@@ -62,7 +62,7 @@ in
};
services.caddy.virtualHosts."files.ganymede".extraConfig = ''
- tls internal
+ import internal
reverse_proxy unix//run/dufs/dufs.sock
'';
diff --git a/modules/services/nixos/ganyupload/README b/modules/services/nixos/ganyupload/README
deleted file mode 100644
index ac9a50d..0000000
--- a/modules/services/nixos/ganyupload/README
+++ /dev/null
@@ -1,13 +0,0 @@
-Ganyupload - File Upload Service
-
-Upload files using curl with a PUT request:
-
- curl -X PUT --data-binary @myfile.txt https://upld.williamsfam.us.com/myfile.txt
-
-The server will respond with "ok" on success.
-
-You can use any path you want:
-
- curl -X PUT --data-binary @document.pdf https://upld.williamsfam.us.com/docs/document.pdf
-
-Nested directories will be created automatically.
diff --git a/modules/services/nixos/ganyupload/default.nix b/modules/services/nixos/ganyupload/default.nix
deleted file mode 100644
index 2ec704b..0000000
--- a/modules/services/nixos/ganyupload/default.nix
+++ /dev/null
@@ -1,46 +0,0 @@
-{
- pkgs,
- config,
- lib,
- ...
-}: let
- cfg = config.collinux.services.ganyupload;
- package = pkgs.callPackage ./pkg.nix {};
-in {
- config = lib.mkIf cfg.enable {
- users.groups."ganyupload" = {};
- users.users."ganyupload" = {
- isSystemUser = true;
- group = "ganyupload";
- home = "/var/lib/ganyupload";
- createHome = true;
- homeMode = "755";
- };
-
- systemd.services."ganyupload" = {
- description = "Ganymede File Upload Service";
- restartIfChanged = true;
- wants = ["network-online.target" "caddy.service"];
- after = ["network-online.target" "caddy.service"];
-
- environment = {
- PORT = toString cfg.port;
- UPLOAD_DIR = "/media/ganyupload";
- };
-
- serviceConfig = {
- User = "ganyupload";
- Type = "simple";
-
- WorkingDirectory = "/var/lib/ganyupload";
- ExecStart = "${package}/bin/ganyupload";
- };
-
- wantedBy = ["multi-user.target"];
- };
-
- services.caddy.virtualHosts."upld.williamsfam.us.com".extraConfig = ''
- reverse_proxy 127.0.0.1:${toString cfg.port}
- '';
- };
-}
diff --git a/modules/services/nixos/ganyupload/go.mod b/modules/services/nixos/ganyupload/go.mod
deleted file mode 100644
index 715e1b7..0000000
--- a/modules/services/nixos/ganyupload/go.mod
+++ /dev/null
@@ -1,3 +0,0 @@
-module git.ganymede/ganyupload
-
-go 1.25.5
diff --git a/modules/services/nixos/ganyupload/main.go b/modules/services/nixos/ganyupload/main.go
deleted file mode 100644
index 75852c2..0000000
--- a/modules/services/nixos/ganyupload/main.go
+++ /dev/null
@@ -1,302 +0,0 @@
-package main
-
-import (
- _ "embed"
- "fmt"
- "html"
- "io"
- "log"
- "net/http"
- "os"
- "path/filepath"
- "sort"
- "strings"
-)
-
-//go:embed README
-var readme string
-
-var uploadDir = "."
-
-// cleanPath safely cleans and validates a path to prevent traversal attacks
-func cleanPath(filename string) (string, error) {
- // Remove leading slash
- filename = strings.TrimPrefix(filename, "/")
- if filename == "" {
- return "", nil
- }
-
- // Clean the path
- filename = filepath.Clean(filename)
-
- // Reject if it tries to go up
- if strings.HasPrefix(filename, "..") || strings.Contains(filename, "/../") {
- return "", fmt.Errorf("invalid filename: path traversal not allowed")
- }
-
- return filename, nil
-}
-
-// isPathSafe checks that the resolved fullPath stays within uploadDir
-func isPathSafe(uploadDir, fullPath string) bool {
- // Resolve both paths to absolute to catch symlink attacks
- absUploadDir, err := filepath.Abs(uploadDir)
- if err != nil {
- return false
- }
- absFullPath, err := filepath.Abs(fullPath)
- if err != nil {
- return false
- }
-
- // Ensure the resolved path starts with uploadDir
- return strings.HasPrefix(absFullPath, absUploadDir+string(filepath.Separator)) ||
- absFullPath == absUploadDir
-}
-
-func handleRoot(w http.ResponseWriter, r *http.Request) {
- if r.Method != http.MethodGet {
- http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
- return
- }
-
- w.Header().Set("Content-Type", "text/plain; charset=utf-8")
- w.Write([]byte(readme))
-}
-
-func handleUpload(w http.ResponseWriter, r *http.Request) {
- // Show README on GET /
- if r.Method == http.MethodGet && r.URL.Path == "/" {
- handleRoot(w, r)
- return
- }
-
- // Handle GET requests for downloads and directory listing
- if r.Method == http.MethodGet {
- handleDownload(w, r)
- return
- }
-
- if r.Method != http.MethodPut {
- http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
- return
- }
-
- // Extract filename from URL path, preventing path traversal
- filename, err := cleanPath(r.URL.Path)
- if err != nil {
- http.Error(w, err.Error(), http.StatusBadRequest)
- return
- }
-
- if filename == "" {
- http.Error(w, "filename required", http.StatusBadRequest)
- return
- }
-
- fullPath := filepath.Join(uploadDir, filename)
-
- // Verify the path is safe
- if !isPathSafe(uploadDir, fullPath) {
- http.Error(w, "invalid path", http.StatusBadRequest)
- return
- }
-
- // Ensure the directory exists
- dir := filepath.Dir(fullPath)
- if err := os.MkdirAll(dir, 0755); err != nil {
- log.Printf("failed to create directory: %v", err)
- http.Error(w, "internal server error", http.StatusInternalServerError)
- return
- }
-
- // Create the file
- f, err := os.Create(fullPath)
- if err != nil {
- log.Printf("failed to create file: %v", err)
- http.Error(w, "internal server error", http.StatusInternalServerError)
- return
- }
- defer f.Close()
-
- // Copy request body to file
- written, err := io.Copy(f, r.Body)
- if err != nil {
- log.Printf("failed to write file: %v", err)
- http.Error(w, "internal server error", http.StatusInternalServerError)
- return
- }
-
- w.Header().Set("Content-Type", "text/plain; charset=utf-8")
- w.WriteHeader(http.StatusCreated)
- w.Write([]byte("ok\n"))
-
- log.Printf("uploaded %s (%d bytes)", filename, written)
-}
-
-func handleDownload(w http.ResponseWriter, r *http.Request) {
- // Extract filename from URL path
- filename, err := cleanPath(r.URL.Path)
- if err != nil {
- http.Error(w, err.Error(), http.StatusBadRequest)
- return
- }
-
- fullPath := filepath.Join(uploadDir, filename)
-
- // Verify the path is safe
- if !isPathSafe(uploadDir, fullPath) {
- http.Error(w, "invalid path", http.StatusBadRequest)
- return
- }
-
- fileInfo, err := os.Stat(fullPath)
- if err != nil {
- if os.IsNotExist(err) {
- http.Error(w, "not found", http.StatusNotFound)
- } else {
- log.Printf("failed to stat: %v", err)
- http.Error(w, "internal server error", http.StatusInternalServerError)
- }
- return
- }
-
- // If it's a directory, list its contents
- if fileInfo.IsDir() {
- handleDirList(w, r, uploadDir, filename, fullPath)
- return
- }
-
- // Serve the file
- http.ServeFile(w, r, fullPath)
-}
-
-func handleDirList(w http.ResponseWriter, r *http.Request, uploadDir, relPath, fullPath string) {
- entries, err := os.ReadDir(fullPath)
- if err != nil {
- log.Printf("failed to read directory: %v", err)
- http.Error(w, "internal server error", http.StatusInternalServerError)
- return
- }
-
- // Sort entries by name
- sort.Slice(entries, func(i, j int) bool {
- return entries[i].Name() < entries[j].Name()
- })
-
- // Build breadcrumb navigation
- pathParts := strings.Split(strings.Trim(relPath, "/"), "/")
- if relPath == "" {
- pathParts = []string{}
- }
-
- w.Header().Set("Content-Type", "text/html; charset=utf-8")
- w.WriteHeader(http.StatusOK)
-
- fmt.Fprintf(w, `<!DOCTYPE html>
-<html>
-<head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Directory: %s</title>
- <style>
- body { font-family: monospace; margin: 2rem; }
- h1 { margin-bottom: 0.5rem; }
- .breadcrumb { margin-bottom: 2rem; font-size: 0.9rem; }
- .breadcrumb a { text-decoration: none; color: #0066cc; }
- .breadcrumb a:hover { text-decoration: underline; }
- table { border-collapse: collapse; width: 100%%; }
- td { padding: 0.5rem; border-bottom: 1px solid #ccc; }
- td:first-child { text-align: left; }
- td:last-child { text-align: right; color: #666; }
- a { text-decoration: none; color: #0066cc; }
- a:hover { text-decoration: underline; }
- .directory { color: #0066cc; font-weight: bold; }
- </style>
-</head>
-<body>
- <h1>Directory: %s</h1>
-`, html.EscapeString(relPath), html.EscapeString(relPath))
-
- // Breadcrumb navigation
- fmt.Fprint(w, ` <div class="breadcrumb">`)
- fmt.Fprintf(w, `<a href="/">home</a>`)
- if len(pathParts) > 0 {
- accumulated := ""
- for _, part := range pathParts {
- accumulated = accumulated + "/" + part
- fmt.Fprintf(w, ` / <a href="%s">%s</a>`, html.EscapeString(accumulated), html.EscapeString(part))
- }
- }
- fmt.Fprint(w, `</div>`)
-
- // Directory listing
- fmt.Fprint(w, ` <table>
-`)
- for _, entry := range entries {
- name := entry.Name()
- safeName := html.EscapeString(name)
-
- if entry.IsDir() {
- // For directories, add trailing slash to the link
- linkPath := relPath + "/" + name
- if relPath == "" {
- linkPath = "/" + name
- }
- linkPath = strings.TrimPrefix(linkPath, "/")
- fmt.Fprintf(w, ` <tr><td><a href="/%s" class="directory">%s/</a></td><td>-</td></tr>
-`, html.EscapeString(linkPath), safeName)
- } else {
- info, _ := entry.Info()
- linkPath := relPath + "/" + name
- if relPath == "" {
- linkPath = "/" + name
- }
- linkPath = strings.TrimPrefix(linkPath, "/")
- size := formatSize(info.Size())
- fmt.Fprintf(w, ` <tr><td><a href="/%s">%s</a></td><td>%s</td></tr>
-`, html.EscapeString(linkPath), safeName, size)
- }
- }
- fmt.Fprint(w, ` </table>
-</body>
-</html>
-`)
-}
-
-func formatSize(bytes int64) string {
- const (
- KB = 1024
- MB = KB * 1024
- GB = MB * 1024
- )
-
- switch {
- case bytes < KB:
- return fmt.Sprintf("%d B", bytes)
- case bytes < MB:
- return fmt.Sprintf("%.1f KB", float64(bytes)/KB)
- case bytes < GB:
- return fmt.Sprintf("%.1f MB", float64(bytes)/MB)
- default:
- return fmt.Sprintf("%.1f GB", float64(bytes)/GB)
- }
-}
-
-func main() {
- if envUploadDir := os.Getenv("UPLOAD_DIR"); envUploadDir != "" {
- uploadDir = envUploadDir
- }
-
- port := os.Getenv("PORT")
- if port == "" {
- port = "8080"
- }
-
- http.HandleFunc("/", handleUpload)
-
- log.Printf("starting ganyupload on port %s, upload directory: %s", port, uploadDir)
- if err := http.ListenAndServe(":"+port, nil); err != nil {
- log.Fatalf("server error: %v", err)
- }
-}
diff --git a/modules/services/nixos/ganyupload/pkg.nix b/modules/services/nixos/ganyupload/pkg.nix
deleted file mode 100644
index 18be8ed..0000000
--- a/modules/services/nixos/ganyupload/pkg.nix
+++ /dev/null
@@ -1,6 +0,0 @@
-{pkgs ? import <nixpkgs> {system = "x86_64-linux";}, ...}:
-pkgs.buildGoModule {
- name = "ganyupload";
- src = ./.;
- vendorHash = null;
-}
diff --git a/modules/services/nixos/glance.nix b/modules/services/nixos/glance.nix
index 2b7ba0b..04b2e09 100644
--- a/modules/services/nixos/glance.nix
+++ b/modules/services/nixos/glance.nix
@@ -156,7 +156,7 @@ in {
};
services.caddy.virtualHosts."home.ganymede".extraConfig = ''
- tls internal
+ import internal
reverse_proxy unix///run/glance/glance.sock
'';
};
diff --git a/modules/services/nixos/goaccess.nix b/modules/services/nixos/goaccess.nix
index 15a3768..c3d2170 100644
--- a/modules/services/nixos/goaccess.nix
+++ b/modules/services/nixos/goaccess.nix
@@ -105,7 +105,7 @@ in {
};
services.caddy.virtualHosts."stats.ganymede".extraConfig = ''
- tls internal
+ import internal
root * /run/goaccess/www
file_server
diff --git a/modules/services/nixos/openssh.nix b/modules/services/nixos/openssh.nix
index eb8bff6..5b21c4f 100644
--- a/modules/services/nixos/openssh.nix
+++ b/modules/services/nixos/openssh.nix
@@ -16,6 +16,7 @@ in {
services.openssh = {
enable = true;
allowSFTP = true;
+ openFirewall = false;
hostKeys = [
{
diff --git a/modules/services/nixos/qbittorrent.nix b/modules/services/nixos/qbittorrent.nix
index eb0e8ad..dda4e11 100644
--- a/modules/services/nixos/qbittorrent.nix
+++ b/modules/services/nixos/qbittorrent.nix
@@ -18,7 +18,7 @@ in {
};
services.caddy.virtualHosts."bittorrent.ganymede".extraConfig = ''
- tls internal
+ import internal
reverse_proxy 127.0.0.1:${toString cfg.port}
'';
diff --git a/modules/services/options.nix b/modules/services/options.nix
index a066836..632e505 100644
--- a/modules/services/options.nix
+++ b/modules/services/options.nix
@@ -18,11 +18,8 @@ in {
default_port = 22;
};
- jta = basicService {desc = "personal project";};
- ganyupload = basicService {desc = "anonymous file uploads";};
btopweb.enable = mkEnableOption "btop accessable in a browser tab";
- 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";
diff --git a/modules/system/nixos/boot.nix b/modules/system/nixos/boot.nix
index 48e3e22..32d0a07 100644
--- a/modules/system/nixos/boot.nix
+++ b/modules/system/nixos/boot.nix
@@ -70,15 +70,10 @@ in {
// (lib.optionalAttrs cfg.secureBoot.enable {
lanzaboote = {
enable = true;
+ configurationLimit = 3;
pkiBundle = "/var/lib/sbctl";
};
});
- # store journald logs in memory
- services.journald.extraConfig = ''
- Storage=volatile
- RuntimeMaxUse=100M
- '';
-
environment.systemPackages = [pkgs.efibootmgr] ++ lib.optional cfg.secureBoot.enable pkgs.sbctl;
}
diff --git a/modules/user/nixos/default.nix b/modules/user/nixos/default.nix
index a6fd52d..2f7a1f6 100644
--- a/modules/user/nixos/default.nix
+++ b/modules/user/nixos/default.nix
@@ -12,7 +12,7 @@ in {
users.${cfg.name} = {
isNormalUser = true;
description = cfg.name;
- extraGroups = ["networkmanager" "disks" "input" "video" "dialout" "kvm"] ++ (lib.optional cfg.isAdmin "wheel");
+ extraGroups = ["networkmanager" "disk" "input" "video" "dialout" "kvm"] ++ (lib.optional cfg.isAdmin "wheel");
};
};
services.userborn.enable = true;