feat(refactor): WIP 2.0 some progress

This commit is contained in:
2025-10-13 21:13:53 +02:00
parent d2bd385367
commit 63f495fa0d
18 changed files with 653 additions and 612 deletions

View File

@@ -1,9 +1,8 @@
{
description = "cnix nix";
outputs =
inputs:
inputs.flake-parts.lib.mkFlake { inherit inputs; } {
outputs = inputs:
inputs.flake-parts.lib.mkFlake {inherit inputs;} {
systems = [
"x86_64-linux"
"aarch64-linux"
@@ -17,25 +16,23 @@
./fmt-hooks.nix
];
perSystem =
{
config,
pkgs,
...
}:
{
devShells.default = pkgs.mkShell {
packages = [
pkgs.git
config.packages.repl
];
name = "dots";
env.DIRENV_LOG_FORMAT = "";
shellHook = ''
${config.pre-commit.installationScript}
'';
};
perSystem = {
config,
pkgs,
...
}: {
devShells.default = pkgs.mkShell {
packages = [
pkgs.git
config.packages.repl
];
name = "dots";
env.DIRENV_LOG_FORMAT = "";
shellHook = ''
${config.pre-commit.installationScript}
'';
};
};
};
inputs = {

View File

@@ -4,114 +4,111 @@
homeImports,
self,
...
}:
{
flake.nixosConfigurations =
let
cLib = import ../lib inputs.nixpkgs.lib;
userConfig = "${self}/home";
systemConfig = "${self}/system";
hostConfig = "${self}/hosts";
}: {
flake.nixosConfigurations = let
cLib = import ../lib inputs.nixpkgs.lib;
userConfig = "${self}/home";
systemConfig = "${self}/system";
hostConfig = "${self}/hosts";
cnstConfig = "${self}/users/cnst";
toothpickConfig = "${self}/users/toothpick";
cnstConfig = "${self}/users/cnst";
toothpickConfig = "${self}/users/toothpick";
umodPath = "${self}/modules/home";
smodPath = "${self}/modules/system";
umodPath = "${self}/modules/home";
smodPath = "${self}/modules/system";
inherit (inputs.nixpkgs.lib) nixosSystem;
inherit (self) outputs;
inherit (inputs.nixpkgs.lib) nixosSystem;
inherit (self) outputs;
specialArgs = {
inherit
cLib
inputs
outputs
self
userConfig
systemConfig
hostConfig
cnstConfig
toothpickConfig
umodPath
smodPath
;
};
in
{
kima = nixosSystem {
inherit specialArgs;
modules = [
./kima
"${self}/nix"
{
home-manager = {
users.cnst.imports = homeImports."cnst@kima";
extraSpecialArgs = specialArgs;
};
}
self.nixosModules.nixos
self.nixosModules.settings
inputs.chaotic.nixosModules.default
inputs.agenix.nixosModules.default
];
};
bunk = nixosSystem {
inherit specialArgs;
modules = [
./bunk
"${self}/nix"
{
home-manager = {
users.cnst.imports = homeImports."cnst@bunk";
extraSpecialArgs = specialArgs;
};
}
self.nixosModules.nixos
self.nixosModules.settings
inputs.chaotic.nixosModules.default
inputs.agenix.nixosModules.default
];
};
sobotka = nixosSystem {
inherit specialArgs;
modules = [
./sobotka
"${self}/nix"
self.nixosModules.nixos
self.nixosModules.settings
self.nixosModules.server
inputs.agenix.nixosModules.default
inputs.authentik.nixosModules.default
];
};
ziggy = nixosSystem {
inherit specialArgs;
modules = [
./ziggy
"${self}/nix"
self.nixosModules.nixos
self.nixosModules.settings
self.nixosModules.server
inputs.agenix.nixosModules.default
];
};
toothpc = nixosSystem {
inherit specialArgs;
modules = [
./toothpc
"${self}/nix"
{
home-manager = {
users.toothpick.imports = homeImports."toothpick@toothpc";
extraSpecialArgs = specialArgs;
};
}
self.nixosModules.nixos
self.nixosModules.settings
inputs.chaotic.nixosModules.default
inputs.agenix.nixosModules.default
];
};
specialArgs = {
inherit
cLib
inputs
outputs
self
userConfig
systemConfig
hostConfig
cnstConfig
toothpickConfig
umodPath
smodPath
;
};
in {
kima = nixosSystem {
inherit specialArgs;
modules = [
./kima
"${self}/nix"
{
home-manager = {
users.cnst.imports = homeImports."cnst@kima";
extraSpecialArgs = specialArgs;
};
}
self.nixosModules.nixos
self.nixosModules.settings
inputs.chaotic.nixosModules.default
inputs.agenix.nixosModules.default
];
};
bunk = nixosSystem {
inherit specialArgs;
modules = [
./bunk
"${self}/nix"
{
home-manager = {
users.cnst.imports = homeImports."cnst@bunk";
extraSpecialArgs = specialArgs;
};
}
self.nixosModules.nixos
self.nixosModules.settings
inputs.chaotic.nixosModules.default
inputs.agenix.nixosModules.default
];
};
sobotka = nixosSystem {
inherit specialArgs;
modules = [
./sobotka
"${self}/nix"
self.nixosModules.nixos
self.nixosModules.settings
self.nixosModules.server
inputs.agenix.nixosModules.default
inputs.authentik.nixosModules.default
];
};
ziggy = nixosSystem {
inherit specialArgs;
modules = [
./ziggy
"${self}/nix"
self.nixosModules.nixos
self.nixosModules.settings
self.nixosModules.server
inputs.agenix.nixosModules.default
];
};
toothpc = nixosSystem {
inherit specialArgs;
modules = [
./toothpc
"${self}/nix"
{
home-manager = {
users.toothpick.imports = homeImports."toothpick@toothpc";
extraSpecialArgs = specialArgs;
};
}
self.nixosModules.nixos
self.nixosModules.settings
inputs.chaotic.nixosModules.default
inputs.agenix.nixosModules.default
];
};
};
}

View File

@@ -55,160 +55,159 @@
};
services = {
# homepage-dashboard = {
# enable = true;
# subdomain = "";
# port = "8082";
# };
# n8n = {
# enable = true;
# subdomain = "n8n";
# port = "5678";
# homepage = {
# name = "n8n";
# description = "A workflow automation platform";
# icon = "n8n.svg";
# category = "Services";
# };
# };
# bazarr = {
# enable = true;
# subdomain = "bazarr";
# port = 6767;
# homepage = {
# name = "Bazarr";
# description = "Subtitle manager";
# icon = "bazarr.svg";
# category = "Arr";
# };
# };
# prowlarr = {
# enable = true;
# subdomain = "prowlarr";
# port = 9696;
# homepage = {
# name = "prowlarr";
# description = "PVR indexer";
# icon = "prowlarr.svg";
# category = "Arr";
# };
# };
# flaresolverr = {
# enable = true;
# subdomain = "flaresolverr";
# port = 8191;
# homepage = {
# name = "FlareSolverr";
# description = "Proxy to bypass Cloudflare/DDoS-GUARD protection";
# icon = "flaresolverr.svg";
# category = "Arr";
# };
# };
# lidarr = {
# enable = true;
# subdomain = "lidarr";
# port = 8686;
# homepage = {
# name = "Lidarr";
# description = "Music collection manager";
# icon = "lidarr.svg";
# category = "Arr";
# };
# };
# sonarr = {
# enable = true;
# subdomain = "sonarr";
# port = 8989;
# homepage = {
# name = "Sonarr";
# description = "Internet PVR for Usenet and Torrents";
# icon = "sonarr.svg";
# category = "Arr";
# };
# };
# radarr = {
# enable = true;
# subdomain = "radarr";
# port = 7878;
# homepage = {
# name = "Radarr";
# description = "Movie collection manager";
# icon = "radarr.svg";
# category = "Arr";
# };
# };
# jellyseerr = {
# enable = true;
# subdomain = "jellyseerr";
# port = 5055;
# homepage = {
# name = "Jellyseerr";
# description = "Media request and discovery manager";
# icon = "jellyserr.svg";
# category = "Arr";
# };
# };
# jellyfin = {
# enable = true;
# subdomain = "fin";
# exposure = "tailscale";
# port = 8096;
# homepage = {
# name = "Jellyfin";
# description = "The Free Software Media System";
# icon = "jellyfin.svg";
# category = "Media";
# };
# };
# uptime-kuma = {
# enable = true;
# subdomain = "uptime";
# port = 3001;
# homepage = {
# name = "Uptime Kuma";
# description = "Service monitoring tool";
# icon = "uptime-kuma.svg";
# category = "Services";
# };
# };
# gitea = {
# enable = true;
# subdomain = "git";
# exposure = "tunnel";
# port = 5003;
# cloudflared = {
# tunnelId = "33e2fb8e-ecef-4d42-b845-6d15e216e448";
# credentialsFile = config.age.secrets.giteaCloudflared.path;
# };
# homepage = {
# name = "Gitea";
# description = "Git with a cup of tea";
# icon = "gitea.svg";
# category = "Services";
# };
# };
homepage-dashboard = {
enable = true;
subdomain = "";
port = 8082;
};
n8n = {
enable = true;
subdomain = "n8n";
port = 5678;
homepage = {
name = "n8n";
description = "A workflow automation platform";
icon = "n8n.svg";
category = "Services";
};
};
bazarr = {
enable = true;
subdomain = "bazarr";
port = 6767;
homepage = {
name = "Bazarr";
description = "Subtitle manager";
icon = "bazarr.svg";
category = "Arr";
};
};
prowlarr = {
enable = true;
subdomain = "prowlarr";
port = 9696;
homepage = {
name = "prowlarr";
description = "PVR indexer";
icon = "prowlarr.svg";
category = "Arr";
};
};
flaresolverr = {
enable = true;
subdomain = "flaresolverr";
port = 8191;
homepage = {
name = "FlareSolverr";
description = "Proxy to bypass Cloudflare/DDoS-GUARD protection";
icon = "flaresolverr.svg";
category = "Arr";
};
};
lidarr = {
enable = true;
subdomain = "lidarr";
port = 8686;
homepage = {
name = "Lidarr";
description = "Music collection manager";
icon = "lidarr.svg";
category = "Arr";
};
};
sonarr = {
enable = true;
subdomain = "sonarr";
port = 8989;
homepage = {
name = "Sonarr";
description = "Internet PVR for Usenet and Torrents";
icon = "sonarr.svg";
category = "Arr";
};
};
radarr = {
enable = true;
subdomain = "radarr";
port = 7878;
homepage = {
name = "Radarr";
description = "Movie collection manager";
icon = "radarr.svg";
category = "Arr";
};
};
jellyseerr = {
enable = true;
subdomain = "jellyseerr";
port = 5055;
homepage = {
name = "Jellyseerr";
description = "Media request and discovery manager";
icon = "jellyserr.svg";
category = "Arr";
};
};
jellyfin = {
enable = true;
subdomain = "fin";
exposure = "tailscale";
port = 8096;
homepage = {
name = "Jellyfin";
description = "The Free Software Media System";
icon = "jellyfin.svg";
category = "Media";
};
};
uptime-kuma = {
enable = true;
subdomain = "uptime";
port = 3001;
homepage = {
name = "Uptime Kuma";
description = "Service monitoring tool";
icon = "uptime-kuma.svg";
category = "Services";
};
};
gitea = {
enable = true;
subdomain = "git";
exposure = "tunnel";
port = 5003;
cloudflared = {
tunnelId = "33e2fb8e-ecef-4d42-b845-6d15e216e448";
credentialsFile = config.age.secrets.giteaCloudflared.path;
};
homepage = {
name = "Gitea";
description = "Git with a cup of tea";
icon = "gitea.svg";
category = "Services";
};
};
vaultwarden = {
enable = true;
# subdomain = "vault";
# exposure = "tunnel";
# port = 8222;
# cloudflared = {
# tunnelId = "fdd98086-6a4c-44f2-bba0-eb86b833cce5";
# credentialsFile = config.age.secrets.vaultwardenCloudflared.path;
# };
# homepage = {
# name = "Vaultwarden";
# description = "Password manager";
# icon = "vaultwarden.svg";
# category = "Services";
# };
subdomain = "vault";
exposure = "tunnel";
port = 8222;
cloudflared = {
tunnelId = "fdd98086-6a4c-44f2-bba0-eb86b833cce5";
credentialsFile = config.age.secrets.vaultwardenCloudflared.path;
};
homepage = {
name = "Vaultwarden";
description = "Password manager";
icon = "vaultwarden.svg";
category = "Services";
};
};
nextcloud = {
enable = true;
subdomain = "cloud";
exposure = "local";
port = 8182;
adminpassFile = config.age.secrets.nextcloudAdminPass.path;
homepage = {
name = "Nextcloud";
description = "A safe home for all your data";
@@ -216,40 +215,40 @@
category = "Services";
};
};
# qbittorrent = {
# enable = true;
# subdomain = "qbt";
# port = 8080;
# homepage = {
# name = "qBittorrent";
# description = "Torrent client";
# icon = "qbittorrent.svg";
# category = "Downloads";
# };
# };
# slskd = {
# enable = true;
# subdomain = "slskd";
# port = 5030;
# homepage = {
# name = "Soulseek";
# description = "Web-based Soulseek client";
# icon = "slskd.svg";
# category = "Downloads";
# };
# };
# pihole = {
# enable = true;
# subdomain = "pihole";
# port = 8053;
# homepage = {
# name = "PiHole";
# description = "Adblocking and DNS service";
# icon = "pi-hole.svg";
# category = "Services";
# path = "/admin";
# };
# };
qbittorrent = {
enable = true;
subdomain = "qbt";
port = 8080;
homepage = {
name = "qBittorrent";
description = "Torrent client";
icon = "qbittorrent.svg";
category = "Downloads";
};
};
slskd = {
enable = true;
subdomain = "slskd";
port = 5030;
homepage = {
name = "Soulseek";
description = "Web-based Soulseek client";
icon = "slskd.svg";
category = "Downloads";
};
};
pihole = {
enable = true;
subdomain = "pihole";
port = 8053;
homepage = {
name = "PiHole";
description = "Adblocking and DNS service";
icon = "pi-hole.svg";
category = "Services";
path = "/admin";
};
};
};
};
}

5
lib/server/default.nix Normal file
View File

@@ -0,0 +1,5 @@
{
imports = [
./serviceurl
];
}

View File

@@ -0,0 +1,23 @@
{
lib,
config,
...
}: let
mkServiceUrl' = import ./serviceurl.nix {inherit config;};
in {
options.clib = {
server = {
mkServiceUrl = lib.mkOption {
type = lib.types.function;
readOnly = true;
description = "Helper function to generate a service URL.";
};
};
};
config.clib = {
server = {
mkServiceUrl = mkServiceUrl';
};
};
}

View File

@@ -0,0 +1,11 @@
{config}: service: let
mainDomain = config.server.networking.domain;
tailscaleDomain = "ts.${mainDomain}";
domain =
if service.exposure == "tunnel"
then mainDomain
else if service.exposure == "tailscale"
then tailscaleDomain
else (service.domain or mainDomain);
in "${service.subdomain}.${domain}"

View File

@@ -123,31 +123,6 @@
server = {
imports = [
./server
./server/infra/authentik
./server/infra/fail2ban
./server/infra/keepalived
./server/infra/podman
./server/infra/postgres
./server/infra/tailscale
./server/infra/traefik
./server/infra/unbound
./server/infra/www
./server/services/bazarr
./server/services/flaresolverr
./server/services/gitea
./server/services/homepage-dashboard
./server/services/jellyfin
./server/services/jellyseerr
./server/services/lidarr
./server/services/n8n
./server/services/nextcloud
./server/services/prowlarr
./server/services/radarr
./server/services/sonarr
./server/services/uptime-kuma
./server/services/vaultwarden
];
};
settings = {

View File

@@ -5,8 +5,7 @@
osConfig,
cLib,
...
}:
let
}: let
inherit (lib) mkIf mkEnableOption;
cfg = osConfig.nixos.programs.hyprland;
@@ -15,8 +14,7 @@ let
#
bg = osConfig.settings.theme.background;
inherit (cLib.theme.bgs) resolve;
in
{
in {
config = mkIf cfg.enable {
programs.hyprlock = {
enable = true;

View File

@@ -5,8 +5,7 @@
osConfig,
cLib,
...
}:
let
}: let
inherit (lib) mkIf;
cfg = osConfig.nixos.programs.hyprland;
@@ -32,8 +31,7 @@ let
bg = bg.primary;
}
];
in
{
in {
config = mkIf cfg.enable {
services.hyprpaper = {
enable = true;

View File

@@ -1,186 +1,8 @@
{
lib,
config,
pkgs,
...
}: let
hardDrives = [
"/dev/disk/by-label/data"
{self, ...}: {
imports = [
"${self}/lib/server"
./options.nix
./infra
./services
];
inherit (lib) mkOption types;
cfg = config.server;
ifTheyExist = groups: builtins.filter (group: builtins.hasAttr group config.users.groups) groups;
in {
options.server = {
enable = lib.mkEnableOption "The server services and configuration variables";
email = mkOption {
default = "";
type = types.str;
description = ''
Email name to be used to access the server services via Caddy reverse proxy
'';
};
domain = mkOption {
default = "";
type = types.str;
description = ''
Domain name to be used to access the server services via Caddy reverse proxy
'';
};
ip = lib.mkOption {
type = lib.types.str;
default = "127.0.0.1";
description = "The local IP of the service.";
};
user = lib.mkOption {
default = "share";
type = lib.types.str;
description = ''
User to run the server services as
'';
};
group = lib.mkOption {
default = "share";
type = lib.types.str;
description = ''
Group to run the server services as
'';
};
uid = lib.mkOption {
default = 1000;
type = lib.types.int;
description = ''
UID to run the server services as
'';
};
gid = lib.mkOption {
default = 1000;
type = lib.types.int;
description = ''
GID to run the server services as
'';
};
timeZone = lib.mkOption {
default = "Europe/Stockholm";
type = lib.types.str;
description = ''
Time zone to be used for the server services
'';
};
services = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule ({name, ...}: {
options = {
enable = lib.mkEnableOption "the service";
subdomain = lib.mkOption {
type = lib.types.str;
default = "";
description = "The subdomain for the service (e.g., 'jellyfin')";
};
exposure = lib.mkOption {
type = lib.types.enum ["local" "tunnel" "tailscale"];
default = "local";
description = "Controls where the service is exposed";
};
port = lib.mkOption {
type = lib.types.int;
default = 80;
description = "The port to host service on.";
};
configDir = lib.mkOption {
type = lib.types.path;
default = "/var/lib/${name}";
description = "Configuration directory for ${name}.";
};
cloudflared = lib.mkOption {
type = lib.types.submodule {
options = {
credentialsFile = lib.mkOption {
type = lib.types.str;
example = "/path/to/cloudflare-credentials.json";
# example = lib.literalExpression ''
# pkgs.writeText "cloudflare-credentials.json" '''
# {"AccountTag":"secret","TunnelSecret":"secret","TunnelID":"secret"}
# '''
# '';
};
tunnelId = lib.mkOption {
type = lib.types.str;
example = "00000000-0000-0000-0000-000000000000";
};
};
};
description = "Cloudflare tunnel configuration for this service.";
};
homepage = lib.mkOption {
type = lib.types.submodule {
options = {
name = lib.mkOption {
type = lib.types.str;
default = "";
description = "Display name on the homepage.";
};
description = lib.mkOption {
type = lib.types.str;
default = "";
description = "A short description for the homepage tile.";
};
icon = lib.mkOption {
type = lib.types.str;
default = "Zervices c00l stuff";
description = "Icon file name for the homepage tile.";
};
category = lib.mkOption {
type = lib.types.str;
default = "";
description = "Homepage category grouping.";
};
path = lib.mkOption {
type = lib.types.str;
default = "";
example = "/admin";
description = "Optional path suffix for homepage links (e.g. /admin).";
};
};
};
description = "Homepage metadata for this service.";
};
};
}));
};
};
config = lib.mkIf cfg.enable {
users = {
groups.${cfg.group} = {
gid = cfg.gid;
};
users.${cfg.user} = {
uid = cfg.uid;
isSystemUser = true;
group = cfg.group;
extraGroups = ifTheyExist [
"audio"
"video"
"docker"
"libvirtd"
"qemu-libvirtd"
"rtkit"
"fail2ban"
"vaultwarden"
"qbittorrent"
"lidarr"
"prowlarr"
"bazarr"
"sonarr"
"radarr"
"media"
"share"
"render"
"input"
"authentik"
"traefik"
];
};
};
};
}

View File

@@ -0,0 +1,13 @@
{
imports = [
./authentik
./fail2ban
./keepalived
./podman
./postgres
./tailscale
./traefik
./unbound
./www
];
}

View File

@@ -11,23 +11,34 @@
srv = config.server;
# Generates all Traefik routers from the central service list
# generateRouters = services:
# lib.mapAttrs' (
# name: service: let
# domain =
# if service.exposure == "tunnel"
# then "cnst.dev"
# else if service.exposure == "tailscale"
# then "ts.cnst.dev"
# else srv.domain;
# in
# lib.nameValuePair "${service.subdomain}" {
# entryPoints = ["websecure"];
# rule = "Host(`${service.subdomain}.${domain}`)";
# service = service.subdomain;
# tls.certResolver = "letsencrypt";
# }
# ) (lib.filterAttrs (name: service: service.enable) services);
generateRouters = services:
lib.mapAttrs' (
name: service: let
domain =
if service.exposure == "tunnel"
then "cnst.dev"
else if service.exposure == "tailscale"
then "ts.cnst.dev"
else srv.domain;
in
name: service:
lib.nameValuePair "${service.subdomain}" {
entryPoints = ["websecure"];
rule = "Host(`${service.subdomain}.${domain}`)";
rule = "Host(`${config.clib.server.mkServiceUrl service}`)";
service = service.subdomain;
tls.certResolver = "letsencrypt";
}
) (lib.filterAttrs (name: service: service.enable) services);
) (lib.filterAttrs (_: s: s.enable) services);
# Generates all Traefik backend services
generateServices = services:

180
modules/server/options.nix Normal file
View File

@@ -0,0 +1,180 @@
{
lib,
config,
...
}: let
inherit (lib) mkOption types;
ifTheyExist = groups: builtins.filter (group: builtins.hasAttr group config.users.groups) groups;
cfg = config.server;
in {
options.server = {
enable = lib.mkEnableOption "The server services and configuration variables";
email = mkOption {
default = "";
type = types.str;
description = ''
Email name to be used to access the server services via Caddy reverse proxy
'';
};
domain = mkOption {
default = "";
type = types.str;
description = ''
Domain name to be used to access the server services via Caddy reverse proxy
'';
};
ip = lib.mkOption {
type = lib.types.str;
default = "127.0.0.1";
description = "The local IP of the service.";
};
user = lib.mkOption {
default = "share";
type = lib.types.str;
description = ''
User to run the server services as
'';
};
group = lib.mkOption {
default = "share";
type = lib.types.str;
description = ''
Group to run the server services as
'';
};
uid = lib.mkOption {
default = 1000;
type = lib.types.int;
description = ''
UID to run the server services as
'';
};
gid = lib.mkOption {
default = 1000;
type = lib.types.int;
description = ''
GID to run the server services as
'';
};
timeZone = lib.mkOption {
default = "Europe/Stockholm";
type = lib.types.str;
description = ''
Time zone to be used for the server services
'';
};
services = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule ({name, ...}: {
options = {
enable = lib.mkEnableOption "the service";
subdomain = lib.mkOption {
type = lib.types.str;
default = "";
description = "The subdomain for the service (e.g., 'jellyfin')";
};
exposure = lib.mkOption {
type = lib.types.enum ["local" "tunnel" "tailscale"];
default = "local";
description = "Controls where the service is exposed";
};
port = lib.mkOption {
type = lib.types.int;
default = 80;
description = "The port to host service on.";
};
configDir = lib.mkOption {
type = lib.types.path;
default = "/var/lib/${name}";
description = "Configuration directory for ${name}.";
};
cloudflared = lib.mkOption {
type = lib.types.submodule {
options = {
credentialsFile = lib.mkOption {
type = lib.types.str;
example = lib.literalExpression ''
pkgs.writeText "cloudflare-credentials.json" '''
{"AccountTag":"secret","TunnelSecret":"secret","TunnelID":"secret"}
'''
'';
};
tunnelId = lib.mkOption {
type = lib.types.str;
example = "00000000-0000-0000-0000-000000000000";
};
};
};
description = "Cloudflare tunnel configuration for this service.";
};
homepage = lib.mkOption {
type = lib.types.submodule {
options = {
name = lib.mkOption {
type = lib.types.str;
default = "";
description = "Display name on the homepage.";
};
description = lib.mkOption {
type = lib.types.str;
default = "";
description = "A short description for the homepage tile.";
};
icon = lib.mkOption {
type = lib.types.str;
default = "Zervices c00l stuff";
description = "Icon file name for the homepage tile.";
};
category = lib.mkOption {
type = lib.types.str;
default = "";
description = "Homepage category grouping.";
};
path = lib.mkOption {
type = lib.types.str;
default = "";
example = "/admin";
description = "Optional path suffix for homepage links (e.g. /admin).";
};
};
};
description = "Homepage metadata for this service.";
};
};
}));
};
};
config = lib.mkIf cfg.enable {
users = {
groups.${cfg.group} = {
gid = cfg.gid;
};
users.${cfg.user} = {
uid = cfg.uid;
isSystemUser = true;
group = cfg.group;
extraGroups = ifTheyExist [
"audio"
"video"
"docker"
"libvirtd"
"qemu-libvirtd"
"rtkit"
"fail2ban"
"vaultwarden"
"qbittorrent"
"lidarr"
"prowlarr"
"bazarr"
"sonarr"
"radarr"
"media"
"share"
"render"
"input"
"authentik"
"traefik"
];
};
};
};
}

View File

@@ -0,0 +1,18 @@
{
imports = [
./bazarr
./flaresolverr
./gitea
./homepage-dashboard
./jellyfin
./jellyseerr
./lidarr
./n8n
./nextcloud
./prowlarr
./radarr
./sonarr
./uptime-kuma
./vaultwarden
];
}

View File

@@ -1,4 +1,3 @@
# "inspired" by @jtojnar <3
{
config,
lib,
@@ -7,21 +6,23 @@
}: let
unit = "gitea";
cfg = config.server.services.${unit};
domain = "${cfg.subdomain}.${config.server.infra.www.url}";
in {
config = lib.mkIf cfg.enable {
age.secrets = {
giteaCloudflared.file = "${self}/secrets/giteaCloudflared.age";
};
age.secrets.giteaCloudflared.file = "${self}/secrets/giteaCloudflared.age";
server.infra = {
fail2ban = {
jails = {
gitea = {
serviceName = "gitea";
failRegex = ''.*(Failed authentication attempt|invalid credentials|Attempted access of unknown user).* from <HOST>'';
};
};
fail2ban.jails.unit = {
serviceName = "${unit}";
failRegex = ''
.*(Failed authentication attempt|invalid credentials|Attempted access of unknown user).*
from <HOST>
'';
};
postgresql.databases = [
{database = unit;}
];
};
services = {
@@ -30,11 +31,11 @@ in {
tunnels.${cfg.cloudflared.tunnelId} = {
credentialsFile = cfg.cloudflared.credentialsFile;
default = "http_status:404";
ingress."${cfg.url}".service = "http://localhost:${toString cfg.port}";
ingress."${domain}".service = "http://localhost:${toString cfg.port}";
};
};
${unit} = {
gitea = {
enable = true;
appName = "cnix code forge";
@@ -46,63 +47,51 @@ in {
createDatabase = false;
};
lfs = {
enable = true;
};
lfs.enable = true;
settings = {
cors = {
ENABLED = true;
SCHEME = "https";
ALLOW_DOMAIN = cfg.url;
};
log = {
MODE = "console";
ALLOW_DOMAIN = domain;
};
log.MODE = "console";
mailer = {
ENABLED = false;
MAILER_TYPE = "sendmail";
FROM = "noreply+adam@cnst.dev";
SENDMAIL_PATH = "/run/wrappers/bin/sendmail";
};
picture = {
DISABLE_GRAVATAR = true;
};
picture.DISABLE_GRAVATAR = true;
repository = {
DEFAULT_BRANCH = "main";
DEFAULT_REPO_UNITS = "repo.code,repo.issues,repo.pulls";
DISABLE_DOWNLOAD_SOURCE_ARCHIVES = true;
};
indexer = {
REPO_INDEXER_ENABLED = true;
};
indexer.REPO_INDEXER_ENABLED = true;
oauth2_client = {
ENABLE_AUTO_REGISTRATION = true;
ACCOUNT_LINKING = "auto";
};
server = {
DOMAIN = cfg.url;
DOMAIN = domain;
LANDING_PAGE = "explore";
HTTP_PORT = cfg.port;
ROOT_URL = "https://${cfg.url}/";
};
security = {
DISABLE_GIT_HOOKS = false;
};
service = {
DISABLE_REGISTRATION = true;
};
session = {
COOKIE_SECURE = true;
ROOT_URL = "https://${domain}/";
};
security.DISABLE_GIT_HOOKS = false;
service.DISABLE_REGISTRATION = true;
session.COOKIE_SECURE = true;
};
};
};
server.infra.postgresql.databases = [
{
database = "gitea";
}
];
};
}

View File

@@ -5,7 +5,7 @@
...
}: let
unit = "homepage-dashboard";
cfg = config.server.services.homepage-dashboard;
cfg = config.server.services.${unit};
srv = config.server;
in {
config = lib.mkIf cfg.enable {
@@ -14,11 +14,14 @@ in {
file = "${self}/secrets/homepageEnvironment.age";
};
};
services = {
glances.enable = true;
${unit} = {
enable = true;
environmentFile = config.age.secrets.homepageEnvironment.path;
settings = {
layout = [
{
@@ -53,10 +56,12 @@ in {
};
}
];
headerStyle = "clean";
statusStyle = "dot";
hideVersion = "true";
};
widgets = [
{
openmeteo = {
@@ -77,6 +82,7 @@ in {
};
}
];
services = let
homepageCategories = [
"Arr"
@@ -84,21 +90,30 @@ in {
"Downloads"
"Services"
];
hl = config.server.services;
homepageServices = x: (lib.attrsets.filterAttrs (
_name: value: value ? homepage && value.homepage.category == x
allServices = srv.services;
homepageServicesFor = category:
lib.filterAttrs
(
name: value:
name
!= unit
&& value ? homepage
&& value.homepage.category == category
)
srv.services);
allServices;
in
lib.lists.forEach homepageCategories (cat: {
"${cat}" =
lib.lists.forEach (lib.attrsets.mapAttrsToList (name: _value: name) (homepageServices "${cat}"))
lib.lists.forEach
(lib.attrsets.mapAttrsToList (name: _value: name) (homepageServicesFor cat))
(x: {
"${hl.${x}.homepage.name}" = {
icon = hl.${x}.homepage.icon;
description = hl.${x}.homepage.description;
href = "https://${hl.${x}.url}";
siteMonitor = "https://${hl.${x}.url}";
"${allServices.${x}.homepage.name}" = {
icon = allServices.${x}.homepage.icon;
description = allServices.${x}.homepage.description;
href = "https://${allServices.${x}.url}";
siteMonitor = "https://${allServices.${x}.url}";
};
});
})

View File

@@ -15,19 +15,15 @@ in {
nextcloudCloudflared.file = "${self}/secrets/nextcloudCloudflared.age";
};
server.infra.fail2ban = lib.mkIf srv.infra.fail2ban.enable {
jails = {
nextcloud = {
serviceName = "${unit}";
_groupsre = ''(?:(?:,?\s*"\w+":(?:"[^"]+"|\w+))*)'';
failRegex = ''
^\{%(_groupsre)s,?\s*"remoteAddr":"<HOST>"%(_groupsre)s,?\s*"message":"Login failed:
^\{%(_groupsre)s,?\s*"remoteAddr":"<HOST>"%(_groupsre)s,?\s*"message":"Two-factor challenge failed:
^\{%(_groupsre)s,?\s*"remoteAddr":"<HOST>"%(_groupsre)s,?\s*"message":"Trusted domain error.
'';
datePattern = '',?\s*"time"\s*:\s*"%%Y-%%m-%%d[T ]%%H:%%M:%%S(%%z)?"'';
};
};
server.infra.fail2ban.jails.nextcloud = {
serviceName = "${unit}";
_groupsre = ''(?:(?:,?\s*"\w+":(?:"[^"]+"|\w+))*)'';
failRegex = ''
^\{%(_groupsre)s,?\s*"remoteAddr":"<HOST>"%(_groupsre)s,?\s*"message":"Login failed:
^\{%(_groupsre)s,?\s*"remoteAddr":"<HOST>"%(_groupsre)s,?\s*"message":"Two-factor challenge failed:
^\{%(_groupsre)s,?\s*"remoteAddr":"<HOST>"%(_groupsre)s,?\s*"message":"Trusted domain error.
'';
datePattern = '',?\s*"time"\s*:\s*"%%Y-%%m-%%d[T ]%%H:%%M:%%S(%%z)?"'';
};
services = {

View File

@@ -7,7 +7,7 @@
}: let
unit = "vaultwarden";
cfg = config.server.services.${unit};
www = config.server.infra.www;
domain = "${cfg.subdomain}.${config.server.infra.www.url}";
in {
config = lib.mkIf cfg.enable {
age.secrets = {
@@ -15,15 +15,9 @@ in {
vaultwardenEnvironment.file = "${self}/secrets/vaultwardenEnvironment.age";
};
server.infra = {
fail2ban = {
jails = {
vaultwarden = {
serviceName = "${unit}";
failRegex = ''^.*?Username or password is incorrect\. Try again\. IP: <ADDR>\. Username:.*$'';
};
};
};
server.infra.fail2ban.jails.${unit} = {
serviceName = "${unit}";
failRegex = ''^.*?Username or password is incorrect\. Try again\. IP: <ADDR>\. Username:.*$'';
};
services = {
@@ -32,7 +26,7 @@ in {
tunnels.${cfg.cloudflared.tunnelId} = {
credentialsFile = cfg.cloudflared.credentialsFile;
default = "http_status:404";
ingress."${cfg.url}".service = "http://localhost:${toString cfg.port}";
ingress."${domain}".service = "http://localhost:${toString cfg.port}";
};
};
@@ -43,7 +37,7 @@ in {
backupDir = "/var/backup/vaultwarden";
config = {
DOMAIN = "https://vault.${www.url}";
DOMAIN = "https://${domain}";
SIGNUPS_ALLOWED = false;
ROCKET_ADDRESS = "127.0.0.1";
ROCKET_PORT = cfg.port;