aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--hosts/ganymede/config.nix26
-rw-r--r--modules/services/nixos/cgit/default.nix82
-rw-r--r--modules/services/nixos/cgit/gitShellCommands.nix43
-rw-r--r--modules/services/nixos/default.nix15
-rw-r--r--modules/services/nixos/goaccess.nix17
-rw-r--r--modules/services/nixos/mkCaddyCfg.nix17
-rw-r--r--modules/services/nixos/qbittorrent.nix24
-rw-r--r--modules/services/options.nix219
8 files changed, 244 insertions, 199 deletions
diff --git a/hosts/ganymede/config.nix b/hosts/ganymede/config.nix
index 03d9804..6d5e34f 100644
--- a/hosts/ganymede/config.nix
+++ b/hosts/ganymede/config.nix
@@ -54,26 +54,38 @@
listenAddr = "0.0.0.0";
rootLogin = true;
}
- # {
- # port = 22;
- # listenAddr = "0.0.0.0";
- # otp = true;
- # }
{
port = 22;
+ listenAddr = "0.0.0.0";
+ otp = true;
+ }
+ {
+ # also enable ipv6
+ port = 22;
listenAddr = "[::]";
}
];
};
- goaccess.enable = true;
- cgit.enable = true;
+ goaccess = {
+ enable = true;
+ privateUrl = "stats.ganymede";
+ };
+
+ cgit = {
+ enable = true;
+ privateUrl = "git.ganymede";
+ };
polaris = {
enable = true;
privateUrl = "music.ganymede";
};
+ qbittorrent = {
+ enable = true;
+ privateUrl = "bittorrent.ganymede";
+ };
copyparty = {
enable = true;
publicUrl = "up.williamsfam.us.com";
diff --git a/modules/services/nixos/cgit/default.nix b/modules/services/nixos/cgit/default.nix
index 0f81958..b35a631 100644
--- a/modules/services/nixos/cgit/default.nix
+++ b/modules/services/nixos/cgit/default.nix
@@ -5,9 +5,26 @@
...
}: let
cfg = config.collinux.services.cgit;
+
+ custom_cgit = pkgs.stdenv.mkDerivation {
+ name = "custom-cgit-assets";
+ src = pkgs.cgit;
+ installPhase = ''
+ mkdir -p $out
+ cp -pPR ./cgit/* $out/
+
+ rm -f $out/cgit.png $out/favicon.ico $out/cgit.css
+ cp -f ${./favicon.svg} $out/favicon.svg
+ cp -f ${./cgit.css} $out/cgit.css
+ '';
+ };
in {
+ imports = [
+ (import ../mkCaddyCfg.nix cfg)
+ ./gitShellCommands.nix
+ ];
+
config = lib.mkIf cfg.enable {
- environment.shells = ["${pkgs.git}/bin/git-shell"];
users.groups."git" = {};
users.users."git" = {
isSystemUser = true;
@@ -20,47 +37,6 @@ in {
openssh.authorizedKeys.keys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIC3SjzIs3YI8PWJaNrAuaEeRcTcvIVHOKyCh2VwHTHEF"];
};
- hjem.users."git".files = {
- "git-shell-commands/set-description" = {
- executable = true;
- text = ''
- #!/usr/bin/env bash
- set -euo pipefail
- repo="$1"
- desc="$2"
- base="/var/lib/cgit"
- repo_path="$base/$repo"
-
- test -d "$repo_path" || { echo "Repository does not exist"; exit 1; }
-
- # Prevent path traversal
- real=$(realpath "$repo_path")
- if [[ "$real" != "$base/"* ]]; then
- echo "Invalid path"
- exit 1
- fi
-
- echo "$desc" | head -n 1 > "$repo_path/description"
-
- echo "Description updated for '$repo'"
- '';
- };
- "git-shell-commands/create-repo" = {
- executable = true;
- text = ''
- #!/usr/bin/env bash
- set -euo pipefail
- repo="$1"
- base="/var/lib/cgit"
- repo_path="$base/$repo"
-
- test -d "$repo_path" && { echo "Repository already exists."; exit 1; }
-
- git init --bare "$repo_path"
- echo "Repository '$repo' created"
- '';
- };
- };
services.openssh.extraConfig = lib.mkAfter ''
Match User git
@@ -84,8 +60,8 @@ in {
root-desc=Git repos associated with Ganymede
readme=:README.md
- about-filter=${pkgs.cgit}/lib/cgit/filters/html-converters/md2html
- source-filter=${pkgs.cgit}/lib/cgit/filters/syntax-highlighting.py
+ about-filter=${custom_cgit}/lib/cgit/filters/html-converters/md2html
+ source-filter=${custom_cgit}/lib/cgit/filters/syntax-highlighting.py
footer=
virtual-root=/
@@ -106,23 +82,7 @@ in {
};
};
- networking.extraHosts = "127.0.0.1 git.ganymede";
- services.caddy.virtualHosts."git.ganymede".extraConfig = let
- custom_cgit = pkgs.stdenv.mkDerivation {
- name = "custom-cgit-assets";
- src = pkgs.cgit;
- installPhase = ''
- mkdir -p $out
- cp -pPR ./cgit/* $out/
-
- rm -f $out/cgit.png $out/favicon.ico $out/cgit.css
- cp -f ${./favicon.svg} $out/favicon.svg
- cp -f ${./cgit.css} $out/cgit.css
- '';
- };
- in ''
- tls internal
-
+ collinux.services.cgit.manualCaddyConfig = ''
@assets path /cgit.css /cgit.js /favicon.svg /robots.txt
handle @assets {
root * ${custom_cgit}
diff --git a/modules/services/nixos/cgit/gitShellCommands.nix b/modules/services/nixos/cgit/gitShellCommands.nix
new file mode 100644
index 0000000..e99f9f2
--- /dev/null
+++ b/modules/services/nixos/cgit/gitShellCommands.nix
@@ -0,0 +1,43 @@
+{
+ hjem.users."git".files = {
+ "git-shell-commands/set-description" = {
+ executable = true;
+ text = ''
+ #!/usr/bin/env bash
+ set -euo pipefail
+ repo="$1"
+ desc="$2"
+ base="/var/lib/cgit"
+ repo_path="$base/$repo"
+
+ test -d "$repo_path" || { echo "Repository does not exist"; exit 1; }
+
+ # Prevent path traversal
+ real=$(realpath "$repo_path")
+ if [[ "$real" != "$base/"* ]]; then
+ echo "Invalid path"
+ exit 1
+ fi
+
+ echo "$desc" | head -n 1 > "$repo_path/description"
+
+ echo "Description updated for '$repo'"
+ '';
+ };
+ "git-shell-commands/create-repo" = {
+ executable = true;
+ text = ''
+ #!/usr/bin/env bash
+ set -euo pipefail
+ repo="$1"
+ base="/var/lib/cgit"
+ repo_path="$base/$repo"
+
+ test -d "$repo_path" && { echo "Repository already exists."; exit 1; }
+
+ git init --bare "$repo_path"
+ echo "Repository '$repo' created"
+ '';
+ };
+ };
+}
diff --git a/modules/services/nixos/default.nix b/modules/services/nixos/default.nix
index 44d0d7f..a46eb27 100644
--- a/modules/services/nixos/default.nix
+++ b/modules/services/nixos/default.nix
@@ -1,15 +1,16 @@
{
imports = [
- # ./mopidy.nix
- ./forgejo.nix
- ./cgit
- ./openssh.nix
- # ./jellyfin.nix
- ./polaris.nix
./caddy.nix
+ ./openssh.nix
+
+ ./forgejo.nix
./goaccess.nix
+ ./cgit
+ ./polaris.nix
+ # ./mopidy.nix
+ # ./jellyfin.nix
./copyparty.nix
- # ./ngircd.nix
+ ./qbittorrent.nix
];
}
diff --git a/modules/services/nixos/goaccess.nix b/modules/services/nixos/goaccess.nix
index 47dbbfa..19b003f 100644
--- a/modules/services/nixos/goaccess.nix
+++ b/modules/services/nixos/goaccess.nix
@@ -16,9 +16,14 @@
log-format = "CADDY";
tz = "America/Chicago";
- ws-url = "wss://stats.ganymede:443/ws"; # url that the html uses to fetch from
+ ws-url =
+ if cfg.publicUrl != null
+ then "wss://${cfg.publicUrl}:443/ws"
+ else if cfg.privateUrl != null
+ then "wss://${cfg.privateUrl}:443/ws"
+ else null;
port = "7890";
- addr = "127.0.0.1";
+ addr = cfg.listenAddr;
real-time-html = "true";
log-file = "/var/log/caddy/access-williamsfam.us.com.log";
@@ -119,15 +124,9 @@ in {
systemd.tmpfiles.rules = [
"d /var/www/goaccess/ 755 goaccess goaccess"
- "Z /var/www/goaccess 755 goaccess goaccess"
];
- networking.extraHosts = ''
- 127.0.0.1 stats.ganymede
- '';
-
- services.caddy.virtualHosts."stats.ganymede".extraConfig = ''
- tls internal
+ collinux.services.goaccess.manualCaddyConfig = ''
root * /var/www/goaccess
file_server
diff --git a/modules/services/nixos/mkCaddyCfg.nix b/modules/services/nixos/mkCaddyCfg.nix
index 770b4d0..02b9a0a 100644
--- a/modules/services/nixos/mkCaddyCfg.nix
+++ b/modules/services/nixos/mkCaddyCfg.nix
@@ -1,18 +1,19 @@
-cfg: {
+cfg: let
+ caddyConfig =
+ if cfg ? reverseProxy && cfg.reverseProxy == true
+ then ''reverse_proxy ${cfg.listenAddr}:${toString cfg.port}''
+ else cfg.manualCaddyConfig;
+in {
networking.extraHosts =
if cfg.privateUrl != null
- then ''
- 127.0.0.1 ${cfg.privateUrl}
- ''
+ then "127.0.0.1 ${cfg.privateUrl}"
else "";
services.caddy.virtualHosts =
(
if cfg.publicUrl != null
then {
- ${cfg.publicUrl}.extraConfig = ''
- reverse_proxy ${cfg.listenAddr}:${toString cfg.port}
- '';
+ ${cfg.publicUrl}.extraConfig = caddyConfig;
}
else {}
)
@@ -21,7 +22,7 @@ cfg: {
then {
"${cfg.privateUrl}".extraConfig = ''
tls internal
- reverse_proxy ${cfg.listenAddr}:${toString cfg.port}
+ ${caddyConfig}
'';
}
else {}
diff --git a/modules/services/nixos/qbittorrent.nix b/modules/services/nixos/qbittorrent.nix
new file mode 100644
index 0000000..af3cdbc
--- /dev/null
+++ b/modules/services/nixos/qbittorrent.nix
@@ -0,0 +1,24 @@
+{
+ lib,
+ config,
+ ...
+}: let
+ cfg = config.collinux.services.qbittorrent;
+in {
+ imports = [
+ (import ./mkCaddyCfg.nix cfg)
+ ];
+
+ config = lib.mkIf cfg.enable {
+ users.users."qbittorrent".extraGroups = ["fileserver"]; # torrent files go to /media/library
+
+ networking.firewall.allowedTCPPorts = [6882];
+ networking.firewall.allowedUDPPorts = [6882];
+
+ services.qbittorrent = {
+ enable = true;
+ webuiPort = cfg.port;
+ torrentingPort = 6882;
+ };
+ };
+}
diff --git a/modules/services/options.nix b/modules/services/options.nix
index 0e250ed..eb88e21 100644
--- a/modules/services/options.nix
+++ b/modules/services/options.nix
@@ -3,132 +3,137 @@
my-lib,
...
}: let
- inherit (lib) mkOption mkEnableOption;
+ inherit (lib) mkOption mkEnableOption types;
inherit (my-lib.netTypes {inherit lib;}) ipAddr;
- selfhostOptions = {
+ # A helper function to generate the submodule
+ webserviceOptions = {
service_name,
default_port ? null,
- }: {
- enable = mkEnableOption "${service_name} selfhosted service";
- serviceName = mkOption {
- internal = true;
- type = lib.types.str;
- default = service_name;
- };
- port = mkOption {
- description = "The port on which ${service_name} will listen for incomming connections";
- type = lib.types.port;
- default = default_port;
- };
- listenAddr = mkOption {
- description = "The IP address on which ${service_name} will listen for incoming connections";
- type = ipAddr;
- default = "127.0.0.1";
- };
- privateUrl = mkOption {
- description = "Internal .local name for the service. Don't put the protocol (https://) in the string";
- type = lib.types.nullOr lib.types.str;
- default = null;
- };
- publicUrl = mkOption {
- description = "Public website on which the service will be hosted. Don't put the protocol (https://) in the string";
- type = lib.types.nullOr lib.types.str;
- default = null;
- };
- };
+ reverse_proxy ? true,
+ }:
+ {
+ enable = mkEnableOption "${service_name} selfhosted service";
+ listenAddr = mkOption {
+ description = "The IP address on which ${service_name} will listen for incoming connections";
+ type = ipAddr;
+ default = "127.0.0.1";
+ };
+ privateUrl = mkOption {
+ description = "Internal .local name for the service. Don't put the protocol (https://) in the string";
+ type = lib.types.nullOr lib.types.str;
+ default = null;
+ };
+ publicUrl = mkOption {
+ description = "Public website on which the service will be hosted. Don't put the protocol (https://) in the string";
+ type = lib.types.nullOr lib.types.str;
+ default = null;
+ };
+ }
+ // (
+ if reverse_proxy
+ then {
+ reverseProxy = mkOption {
+ internal = true;
+ type = lib.types.bool;
+ default = true;
+ };
+ port = mkOption {
+ description = "The port on which ${service_name} will listen for incomming connections";
+ type = lib.types.port;
+ default = default_port;
+ };
+ }
+ else {
+ manualCaddyConfig = mkOption {
+ description = "Configuration to describe this service in caddy, since reverse_proxy = false.";
+ type = lib.types.str;
+ };
+ }
+ );
in {
- options = {
- collinux.services = {
- sshd = {
- enable = mkEnableOption "OpenSSH server";
+ options.collinux.services = {
+ sshd = {
+ enable = mkEnableOption "OpenSSH server";
- portConfig = mkOption {
- description = "List of ssh bind hosts. see submodule options for details";
- type = lib.types.listOf (lib.types.submodule {
- options = {
- port = mkOption {
- description = "Port to run on";
- type = lib.types.port;
- };
-
- listenAddr = mkOption {
- description = "Address to listen on";
- type = lib.types.str;
- default = "127.0.0.1";
- };
+ portConfig = mkOption {
+ description = "List of ssh bind hosts. see submodule options for details";
+ type = lib.types.listOf (lib.types.submodule {
+ options = {
+ port = mkOption {
+ description = "Port to run on";
+ type = lib.types.port;
+ };
- otp = mkEnableOption "Whether to require TOTP (Google Authenticator) 2fa codes for this port";
- rootLogin = mkEnableOption "Whether to allow root login for this port";
+ listenAddr = mkOption {
+ description = "Address to listen on";
+ type = lib.types.str;
+ default = "127.0.0.1";
};
- });
- };
- };
- forgejo = selfhostOptions {
- service_name = "forgejo";
- default_port = 8010;
+ otp = mkEnableOption "Whether to require TOTP (Google Authenticator) 2fa codes for this port";
+ rootLogin = mkEnableOption "Whether to allow root login for this port";
+ };
+ });
};
+ };
- goaccess.enable = mkEnableOption "GoAccess Real-Time Analytics";
+ forgejo = webserviceOptions {
+ service_name = "forgejo";
+ default_port = 8010;
+ };
- cgit.enable = mkEnableOption "cgit web git frontend";
+ goaccess = webserviceOptions {
+ service_name = "goaccess";
+ reverse_proxy = false;
+ };
- # mopidy = {
- # enable = mkEnableOption "Mopidy MPD server";
- # port = mkOption {
- # description = "port to run the service on";
- # type = lib.types.port;
- # default = 6600;
- # };
- # };
+ cgit = webserviceOptions {
+ service_name = "cgit";
+ reverse_proxy = false;
+ };
- polaris = selfhostOptions {
- service_name = "polaris";
- default_port = 8079;
- };
+ polaris = webserviceOptions {
+ service_name = "polaris";
+ default_port = 8079;
+ };
- copyparty =
- (selfhostOptions {
- service_name = "copyparty";
- default_port = 8099;
- })
- // {
- users = mkOption {
- type = lib.types.attrsOf (lib.types.submodule ({config, ...}: {
- options = {
- name = mkOption {
- type = lib.types.str;
- default = config._module.args.name;
- internal = true;
- };
- isAdmin = mkEnableOption "whether this user is an admin";
- passwordFile = mkOption {
- description = "Absolute path to a file containing the password for this user";
- type = lib.types.str;
- example = "/run/secrets.d/copyparty-passwd";
- };
- hasPublicDir = mkEnableOption "give this user a world-readable directory at /public/<username>";
- };
- }));
- };
- };
+ qbittorrent = webserviceOptions {
+ service_name = "qbittorrent";
+ default_port = 8076;
+ };
- ngircd = {
- enable = mkEnableOption "IRC Server";
- port = mkOption {
- type = lib.types.port;
- default = 6667;
+ copyparty =
+ (webserviceOptions {
+ service_name = "copyparty";
+ default_port = 8099;
+ })
+ // {
+ users = mkOption {
+ type = lib.types.attrsOf (lib.types.submodule ({config, ...}: {
+ options = {
+ name = mkOption {
+ type = lib.types.str;
+ default = config._module.args.name;
+ internal = true;
+ };
+ isAdmin = mkEnableOption "whether this user is an admin";
+ passwordFile = mkOption {
+ description = "Absolute path to a file containing the password for this user";
+ type = lib.types.str;
+ example = "/run/secrets.d/copyparty-passwd";
+ };
+ hasPublicDir = mkEnableOption "give this user a world-readable directory at /public/<username>";
+ };
+ }));
};
};
- caddy = {
- enable = mkEnableOption "caddy https server";
- envFile = mkOption {
- description = "Absolute path to file that contains environment variables for caddy operations";
- type = lib.types.str;
- example = "/run/secrets.d/caddy-env";
- };
+ caddy = {
+ enable = mkEnableOption "caddy https server";
+ envFile = mkOption {
+ type = types.str;
+ example = "/run/secrets.d/caddy-env";
};
};
};