move stuff

This commit is contained in:
mjallen18
2025-08-26 17:20:27 -05:00
parent f66c0726b0
commit d15762b199
68 changed files with 24 additions and 25 deletions

View File

@@ -0,0 +1,131 @@
{
config,
pkgs,
lib,
namespace,
...
}:
with lib;
let
cfg = config.${namespace}.services.actual;
dataDir = "/data";
hostAddress = "10.0.1.3";
actualUserId = config.users.users.nix-apps.uid;
actualGroupId = config.users.groups.jallen-nas.gid;
in
{
imports = [ ./options.nix ];
config = mkIf cfg.enable {
containers.actual = {
autoStart = true;
privateNetwork = true;
hostAddress = hostAddress;
localAddress = cfg.localAddress;
bindMounts = {
${dataDir} = {
hostPath = cfg.dataDir;
isReadOnly = false;
};
};
config =
{ lib, ... }:
{
services.actual = {
enable = true;
openFirewall = true;
settings = {
trustedProxies = [ hostAddress ];
port = cfg.port;
dataDir = dataDir;
serverFiles = "${dataDir}/server-files";
userFiles = "${dataDir}/user-files";
};
};
users.users.actual = {
isSystemUser = true;
uid = lib.mkForce actualUserId;
group = "actual";
};
users.groups = {
actual = {
gid = lib.mkForce actualGroupId;
};
};
# System packages
environment.systemPackages = with pkgs; [
sqlite
];
# Create and set permissions for required directories
system.activationScripts.actual-dirs = ''
mkdir -p ${dataDir}
chown -R actual:actual ${dataDir}
chmod -R 0700 ${dataDir}
'';
systemd.services = {
actual = {
environment.ACTUAL_CONFIG_PATH = lib.mkForce "${dataDir}/config.json";
serviceConfig = {
ExecStart = lib.mkForce "${pkgs.actual-server}/bin/actual-server --config ${dataDir}/config.json";
WorkingDirectory = lib.mkForce dataDir;
StateDirectory = lib.mkForce dataDir;
StateDirectoryMode = lib.mkForce 700;
DynamicUser = lib.mkForce false;
ProtectSystem = lib.mkForce null;
};
};
};
networking = {
firewall = {
enable = true;
allowedTCPPorts = [ cfg.port ];
};
# Use systemd-resolved inside the container
# Workaround for bug https://github.com/NixOS/nixpkgs/issues/162686
useHostResolvConf = lib.mkForce false;
};
services.resolved.enable = true;
system.stateVersion = "23.11";
};
};
services.traefik.dynamicConfigOptions = lib.mkIf cfg.reverseProxy.enable {
services.actual.loadBalancer.servers = [
{
url = "http://${cfg.localAddress}:${toString cfg.port}";
}
];
routers.actual = {
entryPoints = [ "websecure" ];
rule = "Host(`${cfg.reverseProxy.host}`)";
service = "actual";
middlewares = cfg.reverseProxy.middlewares;
tls.certResolver = "letsencrypt";
};
};
networking = {
nat = {
forwardPorts = [
{
destination = "${cfg.localAddress}:${toString cfg.port}";
sourcePort = cfg.port;
}
];
};
firewall = {
allowedTCPPorts = [ cfg.port ];
allowedUDPPorts = [ cfg.port ];
};
};
};
}

View File

@@ -0,0 +1,25 @@
{ lib, namespace, ... }:
let
inherit (lib.mjallen) mkOpt mkBoolOpt;
in
with lib;
{
options.${namespace}.services.actual = {
enable = mkEnableOption "actual service";
port = mkOpt types.int 80 "Port for Actual to be hosted on";
localAddress = mkOpt types.str "127.0.0.1" "local address of the service";
dataDir = mkOpt types.str "" "Path to the data dir";
reverseProxy = {
enable = mkBoolOpt false "Enable reverse proxy support";
host = mkOpt types.str "" "Address of the proxy";
middlewares = with types; mkOpt (listOf str) [ ] "List of middlewares to use";
};
};
}

View File

@@ -0,0 +1,71 @@
{
config,
lib,
pkgs,
namespace,
...
}:
with lib;
let
cfg = config.${namespace}.services.ai;
in
{
imports = [ ./options.nix ];
config = mkIf cfg.enable {
services.ollama = {
enable = true;
port = 11434;
host = "0.0.0.0";
user = "nix-apps";
group = "jallen-nas";
openFirewall = true;
acceleration = "cuda";
home = "/media/nas/main/nix-app-data/ollama";
};
environment.systemPackages = [ pkgs.stable.llama-cpp ];
services.llama-cpp = {
enable = true;
port = 8127;
host = "0.0.0.0";
openFirewall = true;
model = "/media/nas/main/nix-app-data/llama-cpp/models/functionary-small-v3.2-GGUF/functionary-small-v3.2.Q4_0.gguf";
package = pkgs.stable.llama-cpp;
extraFlags = [
"--n_gpu-layers"
"500"
"-c"
"0"
"--numa"
"numactl"
"--jinja"
];
};
services.open-webui = {
enable = false;
host = "0.0.0.0";
port = 8888;
openFirewall = true;
# stateDir = "/media/nas/main/nix-app-data/open-webui";
environmentFile = config.sops.secrets."jallen-nas/open-webui".path;
environment = {
OPENID_PROVIDER_URL = "https://authentik.mjallen.dev/application/o/chat/.well-known/openid-configuration";
OAUTH_PROVIDER_NAME = "authentik";
OPENID_REDIRECT_URI = "https://chat.mjallen.dev/oauth/oidc/callback";
ENABLE_OAUTH_SIGNUP = "False";
OAUTH_MERGE_ACCOUNTS_BY_EMAIL = "True";
ENABLE_SIGNUP = "False";
ENABLE_LOGIN_FORM = "False";
ANONYMIZED_TELEMETRY = "False";
DO_NOT_TRACK = "True";
SCARF_NO_ANALYTICS = "True";
OLLAMA_API_BASE_URL = "http://127.0.0.1:11434";
LOCAL_FILES_ONLY = "False";
WEBUI_AUTH = "False";
};
};
};
}

View File

@@ -0,0 +1,7 @@
{ lib, namespace, ... }:
with lib;
{
options.${namespace}.services.ai = {
enable = mkEnableOption "enable ai";
};
}

View File

@@ -0,0 +1,14 @@
{ lib, pkgs, ... }:
{
boot = {
# Enable AppImage
binfmt.registrations.appimage = {
wrapInterpreterInShell = lib.mkDefault false;
interpreter = "${pkgs.appimage-run}/bin/appimage-run";
recognitionType = "magic";
offset = 0;
mask = "\\xff\\xff\\xff\\xff\\x00\\x00\\x00\\x00\\xff\\xff\\xff";
magicOrExtension = "\\x7fELF....AI\\x02";
};
};
}

View File

@@ -0,0 +1,257 @@
{
config,
pkgs,
lib,
namespace,
...
}:
with lib;
let
cfg = config.${namespace}.services.arrs;
radarrDataDir = "/var/lib/radarr";
downloadDir = "/downloads";
incompleteDir = "/downloads-incomplete";
sonarrDataDir = "/var/lib/sonarr";
sabnzbdConfig = "/var/lib/sabnzbd";
jackettDir = "/var/lib/jackett/.config/Jackett";
mediaDir = "/media";
arrUserId = config.users.users.nix-apps.uid;
arrGroupId = config.users.groups.jallen-nas.gid;
radarrPkg = pkgs.radarr;
sonarrPkg = pkgs.sonarr;
delugePkg = pkgs.deluge;
jackettPkg = pkgs.jackett;
in
{
imports = [ ./options.nix ];
config = mkIf cfg.enable {
containers.arrs = {
autoStart = true;
privateNetwork = true;
hostAddress = "10.0.1.3";
localAddress = cfg.localAddress;
config =
{
pkgs,
lib,
...
}:
{
nixpkgs.config = {
allowUnfree = lib.mkForce true;
allowUnfreePredicate =
pkg:
builtins.elem (lib.getName pkg) [
"unrar"
];
};
# Enable radarr service
services.radarr = {
enable = cfg.radarr.enable;
openFirewall = true;
user = "arrs";
group = "media";
dataDir = radarrDataDir;
package = radarrPkg;
};
# Enable Sonarr service
services.sonarr = {
enable = cfg.sonarr.enable;
openFirewall = true;
user = "arrs";
group = "media";
dataDir = sonarrDataDir;
package = sonarrPkg;
};
# Enable Sabnzbd service
services.sabnzbd = {
enable = cfg.sabnzbd.enable;
openFirewall = true;
user = "arrs";
group = "media";
configFile = "${sabnzbdConfig}/sabnzbd.ini";
package = pkgs.sabnzbd;
};
services.deluge = {
enable = cfg.deluge.enable;
user = "arrs";
group = "media";
openFirewall = true;
dataDir = "/media";
package = delugePkg;
web = {
enable = true;
port = cfg.deluge.port;
openFirewall = true;
};
};
services.jackett = {
enable = cfg.jackett.enable;
user = "arrs";
group = "media";
openFirewall = true;
package = jackettPkg;
};
# Create required users and groups
users.users.arrs = {
isSystemUser = true;
uid = lib.mkForce arrUserId;
group = "media";
extraGroups = [ "downloads" ];
};
users.groups = {
media = {
gid = lib.mkForce arrGroupId;
};
downloads = { };
};
# System packages
environment.systemPackages = with pkgs; [
glib
sqlite
mono
mediainfo
protonvpn-cli_2
];
# Create and set permissions for required directories
system.activationScripts.arr-dirs = ''
mkdir -p ${radarrDataDir}
mkdir -p ${sonarrDataDir}
mkdir -p ${sabnzbdConfig}
mkdir -p ${downloadDir}
mkdir -p ${incompleteDir}
mkdir -p ${mediaDir}
chown -R arrs:media ${radarrDataDir}
chown -R arrs:media ${sonarrDataDir}
chown -R arrs:media ${sabnzbdConfig}
chown -R arrs:media ${downloadDir}
chown -R arrs:media ${incompleteDir}
chown -R arrs:media ${mediaDir}
chmod -R 775 ${radarrDataDir}
chmod -R 775 ${sonarrDataDir}
chmod -R 775 ${sabnzbdConfig}
chmod -R 775 ${downloadDir}
chmod -R 775 ${incompleteDir}
chmod -R 775 ${mediaDir}
'';
networking = {
firewall = {
enable = true;
allowedTCPPorts = [
cfg.radarr.port
cfg.sonarr.port
cfg.sabnzbd.port
8080
];
};
# Use systemd-resolved inside the container
# Workaround for bug https://github.com/NixOS/nixpkgs/issues/162686
useHostResolvConf = lib.mkForce false;
};
services.resolved.enable = true;
system.stateVersion = "23.11";
};
# Bind mount directories from host
bindMounts = {
"${radarrDataDir}" = {
hostPath = cfg.radarr.dataDir;
isReadOnly = false;
};
"${sonarrDataDir}" = {
hostPath = cfg.sonarr.dataDir;
isReadOnly = false;
};
"${sabnzbdConfig}" = {
hostPath = cfg.sabnzbd.dataDir;
isReadOnly = false;
};
"${downloadDir}" = {
hostPath = cfg.downloadsDir;
isReadOnly = false;
};
"${incompleteDir}" = {
hostPath = cfg.incompleteDownloadsDir;
isReadOnly = false;
};
"${jackettDir}" = {
hostPath = cfg.jackett.dataDir;
isReadOnly = false;
};
"/media/movies" = {
hostPath = cfg.moviesDir;
isReadOnly = false;
};
"/media/tv" = {
hostPath = cfg.tvDir;
isReadOnly = false;
};
"/media/isos" = {
hostPath = cfg.isosDir;
isReadOnly = false;
};
};
};
networking = {
nat = {
forwardPorts = [
{
destination = "${cfg.localAddress}:${toString cfg.radarr.port}";
sourcePort = cfg.radarr.port;
}
{
destination = "${cfg.localAddress}:${toString cfg.sonarr.port}";
sourcePort = cfg.sonarr.port;
}
{
destination = "${cfg.localAddress}:8080";
sourcePort = cfg.sabnzbd.port;
}
{
destination = "${cfg.localAddress}:${toString cfg.deluge.port}";
sourcePort = cfg.deluge.port;
}
{
destination = "${cfg.localAddress}:${toString cfg.jackett.port}";
sourcePort = cfg.jackett.port;
}
];
};
firewall = {
allowedTCPPorts = [
cfg.radarr.port
cfg.sonarr.port
cfg.sabnzbd.port
8080
cfg.deluge.port
cfg.jackett.port
];
allowedUDPPorts = [
cfg.radarr.port
cfg.sonarr.port
cfg.sabnzbd.port
8080
cfg.deluge.port
cfg.jackett.port
];
};
};
};
}

View File

@@ -0,0 +1,112 @@
{ lib, namespace, ... }:
with lib;
{
options.${namespace}.services.arrs = {
enable = mkEnableOption "arrs services";
radarr = {
enable = mkOption {
type = types.bool;
default = false;
};
port = mkOption {
type = types.int;
default = 7878;
};
dataDir = mkOption {
type = types.str;
default = "";
};
};
sonarr = {
enable = mkOption {
type = types.bool;
default = false;
};
port = mkOption {
type = types.int;
default = 8989;
};
dataDir = mkOption {
type = types.str;
default = "";
};
};
sabnzbd = {
enable = mkOption {
type = types.bool;
default = false;
};
port = mkOption {
type = types.int;
default = 8280;
};
dataDir = mkOption {
type = types.str;
default = "";
};
};
deluge = {
enable = mkOption {
type = types.bool;
default = false;
};
port = mkOption {
type = types.int;
default = 8112;
};
dataDir = mkOption {
type = types.str;
default = "";
};
};
jackett = {
enable = mkOption {
type = types.bool;
default = false;
};
port = mkOption {
type = types.int;
default = 9117;
};
dataDir = mkOption {
type = types.str;
default = "";
};
};
localAddress = mkOption {
type = types.str;
default = "127.0.0.1";
};
downloadsDir = mkOption {
type = types.str;
default = "";
};
incompleteDownloadsDir = mkOption {
type = types.str;
default = "";
};
moviesDir = mkOption {
type = types.str;
default = "";
};
tvDir = mkOption {
type = types.str;
default = "";
};
isosDir = mkOption {
type = types.str;
default = "";
};
};
}

View File

@@ -0,0 +1,155 @@
{
config,
lib,
pkgs,
namespace,
...
}:
with lib;
let
cfg = config.${namespace}.services.attic;
in
{
imports = [ ./options.nix ];
config = mkIf cfg.enable {
services.atticd = {
enable = true;
environmentFile = cfg.environmentFile;
settings = {
listen = "${cfg.listenAddress}:${toString cfg.port}";
};
};
# Open firewall for attic if enabled
networking.firewall = mkIf cfg.openFirewall {
allowedTCPPorts = [ cfg.port ];
allowedUDPPorts = [ cfg.port ];
};
# Include the attic watch-store service and rebuild cache services
systemd.services = {
attic-watch-store = {
enable = true;
description = "watch store for cache";
serviceConfig = {
Type = "simple";
User = "admin";
Group = "jallen-nas";
WorkingDirectory = "/etc/nixos";
StandardOutput = "journal+console";
StandardError = "journal+console";
Restart = "always";
RestartSec = "5";
};
path = with pkgs; [
bash
attic-client
];
script = ''
#!/usr/bin/env bash
attic watch-store nas-cache
'';
};
nix-rebuild-cache = {
enable = true;
description = "Rebuild NixOS configurations for cache";
serviceConfig = {
Type = "oneshot";
User = "admin";
Group = "jallen-nas";
WorkingDirectory = "/etc/nixos";
StandardOutput = "journal+console";
StandardError = "journal+console";
Restart = "no";
TimeoutStartSec = "2h";
};
path = with pkgs; [
nix
git
coreutils
gnugrep
gnused
openssh
];
script = ''
#!/usr/bin/env bash
if [ -d .git ]; then
git pull || echo "Warning: Could not pull latest changes"
fi
echo "Updating flake at $(date)"
if nix flake update; then
echo "flake updated successfully at $(date)"
else
echo "failed to update flake $(date)"
fi
if NIXPKGS_ALLOW_UNFREE=1 nix flake check --impure; then
echo "flake checked successfully at $(date)"
else
echo "flake check failed at $(date)"
git reset --hard
fi
if nh os build --hostname=nas; then
echo "nas built successfully at $(date)"
fi;
if nh os build --hostname=nuc; then
echo "nuc built successfully at $(date)"
fi;
if nh os build --hostname=desktop; then
echo "desktop built successfully at $(date)"
fi;
if nh os build --hostname=steamdeck; then
echo "steamdeck built successfully at $(date)"
fi;
if nh os build --hostname=pi4; then
echo "pi4 built successfully at $(date)"
fi;
if nh os build --hostname=pi5; then
echo "pi5 built successfully at $(date)"
fi;
'';
};
};
# Include timers for cache rebuilds
systemd.timers = {
nix-rebuild-cache = {
description = "Timer for rebuilding NixOS configurations cache";
wantedBy = [ "timers.target" ];
timerConfig = {
OnCalendar = "weekly";
Persistent = true;
RandomizedDelaySec = "24h";
};
};
};
# Configure distributed builds
nix = {
settings.builders-use-substitutes = true;
distributedBuilds = true;
buildMachines = [
{
hostName = "pi5.local";
system = "aarch64-linux";
maxJobs = 4;
sshUser = "matt";
supportedFeatures = [
"nixos-test"
"benchmark"
"big-parallel"
"kvm"
];
}
];
};
};
}

View File

@@ -0,0 +1,31 @@
{ lib, namespace, ... }:
with lib;
{
options.${namespace}.services.attic = {
enable = mkEnableOption "attic binary cache daemon";
port = mkOption {
type = types.port;
default = 9012;
description = "Port for attic cache daemon";
};
openFirewall = mkOption {
type = types.bool;
default = true;
description = "Whether to open firewall for attic";
};
environmentFile = mkOption {
type = types.nullOr types.path;
default = null;
description = "Path to environment file containing attic secrets";
};
listenAddress = mkOption {
type = types.str;
default = "[::1]";
description = "Address to listen on";
};
};
}

View File

@@ -0,0 +1,47 @@
{
config,
lib,
namespace,
...
}:
with lib;
let
cfg = config.${namespace}.services.authentik;
in
{
imports = [ ./options.nix ];
config = mkIf cfg.enable {
services.authentik = {
enable = true;
environmentFile = cfg.environmentFile;
settings = {
port = cfg.port;
};
};
# Open firewall for authentik if enabled
networking.firewall = mkIf cfg.openFirewall {
allowedTCPPorts = [ cfg.port ];
allowedUDPPorts = [ cfg.port ];
};
# Ensure PostgreSQL is configured for authentik
services.postgresql = {
enable = mkDefault true;
ensureDatabases = [ "authentik" ];
ensureUsers = [
{
name = "authentik";
ensureDBOwnership = true;
}
];
};
# Ensure Redis is configured for authentik
services.redis.servers.authentik = {
enable = mkDefault true;
port = mkDefault 6379;
};
};
}

View File

@@ -0,0 +1,31 @@
{ lib, namespace, ... }:
with lib;
{
options.${namespace}.services.authentik = {
enable = mkEnableOption "authentik identity provider";
port = mkOption {
type = types.port;
default = 9000;
description = "Port for authentik web interface";
};
openFirewall = mkOption {
type = types.bool;
default = true;
description = "Whether to open firewall for authentik";
};
environmentFile = mkOption {
type = types.nullOr types.path;
default = null;
description = "Path to environment file containing authentik secrets";
};
dataDir = mkOption {
type = types.str;
default = "/var/lib/authentik";
description = "Data directory for authentik";
};
};
}

View File

@@ -0,0 +1,37 @@
{
config,
lib,
namespace,
...
}:
with lib;
let
cfg = config.${namespace}.services.code-server;
in
{
imports = [ ./options.nix ];
config = mkIf cfg.enable {
# Configure the standard NixOS code-server service
services.code-server = {
enable = true;
port = cfg.port;
user = cfg.user;
group = cfg.group;
host = cfg.host;
auth = cfg.auth;
disableTelemetry = cfg.disableTelemetry;
disableUpdateCheck = cfg.disableUpdateCheck;
extraEnvironment = cfg.extraEnvironment;
}
// optionalAttrs (cfg.hashedPassword != null) {
hashedPassword = cfg.hashedPassword;
};
# Open firewall for code-server if enabled
networking.firewall = mkIf cfg.openFirewall {
allowedTCPPorts = [ cfg.port ];
allowedUDPPorts = [ cfg.port ];
};
};
}

View File

@@ -0,0 +1,70 @@
{ lib, namespace, ... }:
with lib;
{
options.${namespace}.services.code-server = {
enable = mkEnableOption "code-server with enhanced configuration";
port = mkOption {
type = types.port;
default = 4444;
description = "Port for code-server";
};
openFirewall = mkOption {
type = types.bool;
default = true;
description = "Whether to open firewall for code-server";
};
user = mkOption {
type = types.str;
default = "admin";
description = "User to run code-server as";
};
group = mkOption {
type = types.str;
default = "users";
description = "Group to run code-server as";
};
host = mkOption {
type = types.str;
default = "0.0.0.0";
description = "Host to bind code-server to";
};
auth = mkOption {
type = types.enum [
"none"
"password"
];
default = "none";
description = "Authentication method for code-server";
};
hashedPassword = mkOption {
type = types.nullOr types.str;
default = null;
description = "Hashed password for code-server authentication";
};
extraEnvironment = mkOption {
type = types.attrsOf types.str;
default = { };
description = "Extra environment variables for code-server";
};
disableTelemetry = mkOption {
type = types.bool;
default = true;
description = "Whether to disable telemetry";
};
disableUpdateCheck = mkOption {
type = types.bool;
default = true;
description = "Whether to disable update checks";
};
};
}

View File

@@ -0,0 +1,66 @@
{
config,
lib,
pkgs,
namespace,
...
}:
with lib;
let
cfg = config.${namespace}.services.crowdsec;
in
{
imports = [ ./options.nix ];
config = lib.mkIf cfg.enable {
services = {
crowdsec =
let
yaml = (pkgs.formats.yaml { }).generate;
acquisitions_file = yaml "acquisitions.yaml" {
source = "journalctl";
journalctl_filter = [ "_SYSTEMD_UNIT=sshd.service" ];
labels.type = "syslog";
};
in
{
enable = true;
enrollKeyFile = "${cfg.dataDir}/enroll.key";
settings = {
crowdsec_service.acquisition_path = acquisitions_file;
api.server = {
listen_uri = "0.0.0.0:${toString cfg.port}";
};
};
};
crowdsec-firewall-bouncer = {
enable = true;
settings = {
api_key = cfg.apiKey;
api_url = "http://${cfg.apiAddress}:${toString cfg.port}";
};
};
};
systemd.services.crowdsec.serviceConfig = {
ExecStartPre =
let
script = pkgs.writeScriptBin "register-bouncer" ''
#!${pkgs.runtimeShell}
set -eu
set -o pipefail
if ! cscli bouncers list | grep -q "nas-bouncer"; then
cscli bouncers add "nas-bouncer" --key "${cfg.apiKey}"
fi
'';
in
[ "${script}/bin/register-bouncer" ];
};
networking.firewall = mkIf cfg.openFirewall {
allowedTCPPorts = [ cfg.port ];
allowedUDPPorts = [ cfg.port ];
};
};
}

View File

@@ -0,0 +1,37 @@
{ lib, namespace, ... }:
with lib;
{
options.${namespace}.services.crowdsec = {
enable = mkEnableOption "crowdsec service";
port = mkOption {
type = types.port;
default = 9898;
description = "Port for crowdsec API";
};
openFirewall = mkOption {
type = types.bool;
default = true;
description = "Whether to open firewall for crowdsec";
};
apiAddress = mkOption {
type = types.str;
default = "127.0.0.1";
description = "API address for crowdsec";
};
apiKey = mkOption {
type = types.str;
default = "";
description = "API key for crowdsec bouncer";
};
dataDir = mkOption {
type = types.str;
default = "";
description = "Data directory for crowdsec";
};
};
}

View File

@@ -0,0 +1,145 @@
{
config,
lib,
namespace,
...
}:
with lib;
let
cfg = config.${namespace}.services.gitea;
hostAddress = "10.0.1.3";
# localAddress = "10.0.4.18";
# httpPort = 3000;
# sshPort = 2222;
rootUrl = "https://gitea.mjallen.dev/";
# stateDir = "/media/nas/main/nix-app-data/gitea";
dataDir = "/var/lib/gitea";
secretsDir = "/run/secrets/jallen-nas/gitea";
mailerPasswordFile = config.sops.secrets."jallen-nas/gitea/mail-key".path;
metricsTokenFile = config.sops.secrets."jallen-nas/gitea/metrics-key".path;
in
{
imports = [ ./options.nix ];
config = mkIf cfg.enable {
containers.gitea = {
autoStart = true;
privateNetwork = true;
hostAddress = hostAddress;
localAddress = cfg.localAddress;
bindMounts = {
${dataDir} = {
hostPath = cfg.dataDir;
isReadOnly = false;
};
secrets = {
hostPath = secretsDir;
isReadOnly = true;
mountPoint = secretsDir;
};
};
config =
{ lib, ... }:
{
services.gitea = {
enable = true;
stateDir = dataDir;
mailerPasswordFile = mailerPasswordFile;
metricsTokenFile = metricsTokenFile;
settings = {
server = {
DOMAIN = "jallen-nas";
HTTP_ADDR = "0.0.0.0";
HTTP_PORT = cfg.httpPort;
PROTOCOL = "http";
ROOT_URL = rootUrl;
START_SSH_SERVER = true;
SSH_PORT = cfg.sshPort;
};
service = {
REGISTER_EMAIL_CONFIRM = false;
ENABLE_CAPTCHA = false;
DISABLE_REGISTRATION = true;
ENABLE_OPENID_SIGNIN = false;
ENABLE_LDAP_SIGNIN = false;
ENABLE_SSH_SIGNIN = true;
ENABLE_BUILTIN_SSH_SERVER = true;
ENABLE_REVERSE_PROXY_AUTHENTICATION = true;
};
};
};
users.users.gitea = {
extraGroups = [ "keys" ];
};
networking = {
firewall = {
enable = true;
allowedTCPPorts = [
cfg.httpPort
cfg.sshPort
];
};
# Use systemd-resolved inside the container
# Workaround for bug https://github.com/NixOS/nixpkgs/issues/162686
useHostResolvConf = lib.mkForce false;
};
# Create and set permissions for required directories
system.activationScripts.gitea-dirs = ''
mkdir -p /var/lib/gitea
chown -R gitea:gitea /var/lib/gitea
chmod -R 775 /var/lib/gitea
mkdir -p /run/secrets/jallen-nas
chown -R gitea:gitea /run/secrets/jallen-nas
chmod -R 775 /run/secrets/jallen-nas
'';
services.resolved.enable = true;
system.stateVersion = "23.11";
};
};
services.traefik.dynamicConfigOptions = lib.mkIf cfg.reverseProxy.enable {
services.gitea.loadBalancer.servers = [
{
url = "http://${cfg.localAddress}:${toString cfg.httpPort}";
}
];
routers.gitea = {
entryPoints = [ "websecure" ];
rule = "Host(`${cfg.reverseProxy.host}`)";
service = "gitea";
middlewares = cfg.reverseProxy.middlewares;
tls.certResolver = "letsencrypt";
};
};
networking = {
nat = {
forwardPorts = [
{
destination = "${cfg.localAddress}:${toString cfg.httpPort}";
sourcePort = cfg.httpPort;
}
{
destination = "${cfg.localAddress}:${toString cfg.sshPort}";
sourcePort = cfg.sshPort;
}
];
};
firewall = {
allowedTCPPorts = [
cfg.httpPort
cfg.sshPort
];
allowedUDPPorts = [
cfg.httpPort
cfg.sshPort
];
};
};
};
}

View File

@@ -0,0 +1,42 @@
{ lib, namespace, ... }:
with lib;
{
options.${namespace}.services.gitea = {
enable = mkEnableOption "gitea service";
httpPort = mkOption {
type = types.int;
default = 80;
};
sshPort = mkOption {
type = types.int;
default = 22;
};
localAddress = mkOption {
type = types.str;
default = "127.0.0.1";
};
dataDir = mkOption {
type = types.str;
default = "";
};
reverseProxy = {
enable = mkOption {
type = types.bool;
default = false;
};
host = mkOption {
type = types.str;
default = "";
};
middlewares = mkOption {
type = with types; listOf str;
default = [ ];
};
};
};
}

View File

@@ -0,0 +1,63 @@
{
config,
lib,
pkgs,
namespace,
...
}:
with lib;
let
cfg = config.${namespace}.services.glances;
in
{
imports = [ ./options.nix ];
config = mkIf cfg.enable {
# Open firewall for glances if enabled
networking.firewall = mkIf cfg.openFirewall {
allowedTCPPorts = [ cfg.port ];
allowedUDPPorts = [ cfg.port ];
};
# Install glances package
environment.systemPackages = with pkgs; [
glances
];
# Configure systemd service for glances
systemd.services.glances-server = {
description = "Glances system monitoring web server";
enable = true;
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
path = with pkgs; [
bash
glances
];
script = ''
glances -w --bind ${cfg.bindAddress} --port ${toString cfg.port}
'';
serviceConfig = {
Type = "simple";
User = "glances";
Group = "glances";
Restart = "always";
RestartSec = "5";
StandardOutput = "journal";
StandardError = "journal";
};
};
# Create glances user and group
users.users.glances = {
isSystemUser = true;
group = "glances";
description = "Glances monitoring user";
};
users.groups.glances = { };
};
}

View File

@@ -0,0 +1,25 @@
{ lib, namespace, ... }:
with lib;
{
options.${namespace}.services.glances = {
enable = mkEnableOption "glances system monitoring service";
port = mkOption {
type = types.port;
default = 61208;
description = "Port for glances web interface";
};
openFirewall = mkOption {
type = types.bool;
default = true;
description = "Whether to open firewall for glances";
};
bindAddress = mkOption {
type = types.str;
default = "0.0.0.0";
description = "Address to bind glances web server to";
};
};
}

View File

@@ -0,0 +1,38 @@
{
config,
lib,
namespace,
...
}:
with lib;
let
cfg = config.${namespace}.services.immich;
immichPort = 2283;
dataDir = "/media/nas/main/photos";
dbPassword = config.sops.secrets."jallen-nas/immich/db-password".path;
in
{
imports = [ ./options.nix ];
config = mkIf cfg.enable {
# Enable immich service
services.immich = {
enable = true;
port = immichPort;
openFirewall = true;
secretsFile = dbPassword;
mediaLocation = dataDir;
environment = {
IMMICH_HOST = lib.mkForce "0.0.0.0";
IMMICH_TRUSTED_PROXIES = "10.0.1.3";
TZ = "America/Chicago";
};
machine-learning = {
enable = true;
};
};
};
}

View File

@@ -0,0 +1,7 @@
{ lib, namespace, ... }:
with lib;
{
options.${namespace}.services.immich = {
enable = mkEnableOption "enable immich";
};
}

View File

@@ -0,0 +1,24 @@
{
config,
lib,
namespace,
...
}:
with lib;
let
cfg = config.${namespace}.services.jellyfin;
in
{
imports = [ ./options.nix ];
config = mkIf cfg.enable {
services.jellyfin = {
enable = true;
openFirewall = true;
user = "nix-apps";
group = "jallen-nas";
dataDir = "/media/nas/main/nix-app-data/jellyfin";
# cacheDir = "/cache";
};
};
}

View File

@@ -0,0 +1,7 @@
{ lib, namespace, ... }:
with lib;
{
options.${namespace}.services.jellyfin = {
enable = mkEnableOption "enable jellyfin";
};
}

View File

@@ -0,0 +1,83 @@
{
config,
lib,
namespace,
...
}:
with lib;
let
cfg = config.${namespace}.services.jellyseerr;
jellyseerrPort = 5055;
dataDir = "/var/lib/private/jellyseerr";
in
{
imports = [ ./options.nix ];
config = mkIf cfg.enable {
containers.jellyseerr = {
autoStart = true;
privateNetwork = true;
hostAddress = "10.0.1.3";
localAddress = "10.0.1.52";
hostAddress6 = "fc00::1";
localAddress6 = "fc00::4";
bindMounts = {
${dataDir} = {
hostPath = "/media/nas/main/nix-app-data/jellyseerr";
isReadOnly = false;
};
};
config =
{
lib,
...
}:
{
# Enable jellyseerr service
services.jellyseerr = {
enable = true;
port = jellyseerrPort;
# package = package;
openFirewall = true;
};
networking = {
firewall = {
enable = true;
allowedTCPPorts = [ jellyseerrPort ];
};
# Use systemd-resolved inside the container
# Workaround for bug https://github.com/NixOS/nixpkgs/issues/162686
useHostResolvConf = lib.mkForce false;
};
# Create and set permissions for required directories
system.activationScripts.jellyseerr-dirs = ''
mkdir -p /var/lib/private/jellyseerr
chown -R jellyseerr:jellyseerr /var/lib/private/jellyseerr
chmod -R 775 /var/lib/private/jellyseerr
ln -sf /var/lib/private/jellyseerr /var/lib/jellyfin
'';
services.resolved.enable = true;
system.stateVersion = "23.11";
};
};
networking.nat = {
forwardPorts = [
{
destination = "10.0.1.52:5055";
sourcePort = jellyseerrPort;
}
];
};
};
}

View File

@@ -0,0 +1,7 @@
{ lib, namespace, ... }:
with lib;
{
options.${namespace}.services.jellyseerr = {
enable = mkEnableOption "enable jellyseerr";
};
}

View File

@@ -0,0 +1,39 @@
{
config,
lib,
namespace,
...
}:
with lib;
let
cfg = config.${namespace}.services.lubelogger;
in
{
imports = [ ./options.nix ];
config = mkIf cfg.enable {
virtualisation.oci-containers.containers.lubelogger = {
autoStart = true;
image = "ghcr.io/hargata/lubelogger";
ports = [ "${toString cfg.port}:8080" ];
volumes = [
"/media/nas/main/nix-app-data/lubelogger:/App/data"
"/media/nas/main/nix-app-data/lubelogger/keys:/root/.aspnet/DataProtection-Keys"
];
environmentFiles = [
"/media/nas/main/nix-app-data/lubelogger/lubelogger.env"
];
environment = {
PUID = toString config.users.users.nix-apps.uid;
PGID = toString config.users.groups.jallen-nas.gid;
TZ = "America/Chicago";
};
};
# Open firewall for lubelogger if enabled
networking.firewall = mkIf cfg.openFirewall {
allowedTCPPorts = [ cfg.port ];
allowedUDPPorts = [ cfg.port ];
};
};
}

View File

@@ -0,0 +1,19 @@
{ lib, namespace, ... }:
with lib;
{
options.${namespace}.services.lubelogger = {
enable = mkEnableOption "enable lubelogger";
port = mkOption {
type = types.port;
default = 6754;
description = "Port for lubelogger web interface";
};
openFirewall = mkOption {
type = types.bool;
default = true;
description = "Whether to open firewall for lubelogger";
};
};
}

View File

@@ -0,0 +1,45 @@
{
config,
lib,
namespace,
...
}:
with lib;
let
cfg = config.${namespace}.services.netbootxyz;
in
{
imports = [ ./options.nix ];
config = mkIf cfg.enable {
# Open firewall for netbootxyz if enabled
networking.firewall = mkIf cfg.openFirewall {
allowedTCPPorts = [
cfg.webPort
cfg.assetPort
cfg.tftpPort
];
allowedUDPPorts = [
cfg.webPort
cfg.assetPort
cfg.tftpPort
];
};
virtualisation.oci-containers = {
containers.netbootxyz = {
autoStart = true;
image = "ghcr.io/netbootxyz/netbootxyz:latest";
ports = [
"${toString cfg.webPort}:3000"
"${toString cfg.assetPort}:80"
"${toString cfg.tftpPort}:69"
];
volumes = [
"${cfg.dataDir}:/config"
"${cfg.assetDir}:/assets"
];
};
};
};
}

View File

@@ -0,0 +1,43 @@
{ lib, namespace, ... }:
with lib;
{
options.${namespace}.services.netbootxyz = {
enable = mkEnableOption "netbootxyz network boot service";
webPort = mkOption {
type = types.port;
default = 4000;
description = "HTTP port for netbootxyz";
};
assetPort = mkOption {
type = types.port;
default = 4001;
description = "NGINX server for hosting assets.";
};
tftpPort = mkOption {
type = types.port;
default = 69;
description = "HTTPS port for netbootxyz";
};
openFirewall = mkOption {
type = types.bool;
default = true;
description = "Whether to open firewall for netbootxyz";
};
dataDir = mkOption {
type = types.str;
default = "/media/nas/main/nix-app-data/netbootxyz";
description = "Data directory for netbootxyz";
};
assetDir = mkOption {
type = types.str;
default = "/media/nas/main/isos";
description = "Asset directory for netbootxyz";
};
};
}

View File

@@ -0,0 +1,249 @@
{
config,
lib,
namespace,
...
}:
with lib;
let
cfg = config.${namespace}.services.nextcloud;
adminpass = config.sops.secrets."jallen-nas/nextcloud/adminpassword".path;
secretsFile = config.sops.secrets."jallen-nas/nextcloud/smtp_settings".path;
jwtSecretFile = config.sops.secrets."jallen-nas/onlyoffice-key".path;
nextcloudUserId = config.users.users.nix-apps.uid;
nextcloudGroupId = config.users.groups.jallen-nas.gid;
hostAddress = "10.0.1.3";
localAddress = "10.0.2.18";
nextcloudPortExtHttp = 9988;
nextcloudPortExtHttps = 9943;
onlyofficePortExt = 9943;
in
{
imports = [ ./options.nix ];
config = mkIf cfg.enable {
containers.nextcloud = {
autoStart = true;
privateNetwork = true;
hostAddress = hostAddress;
localAddress = localAddress;
specialArgs = {
inherit namespace;
};
bindMounts = {
secrets = {
hostPath = "/run/secrets/jallen-nas/nextcloud";
isReadOnly = true;
mountPoint = "/run/secrets/jallen-nas/nextcloud";
};
secrets2 = {
hostPath = "/run/secrets/jallen-nas/onlyoffice-key";
isReadOnly = true;
mountPoint = "/run/secrets/jallen-nas/onlyoffice-key";
};
data = {
hostPath = "/media/nas/main/nextcloud";
isReadOnly = false;
mountPoint = "/data";
};
"/var/lib/nextcloud" = {
hostPath = "/media/nas/main/nix-app-data/nextcloud";
isReadOnly = false;
mountPoint = "/var/lib/nextcloud";
};
"/var/lib/onlyoffice" = {
hostPath = "/media/nas/main/nix-app-data/onlyoffice";
isReadOnly = false;
mountPoint = "/var/lib/onlyoffice";
};
};
config =
{
pkgs,
lib,
...
}:
{
nixpkgs.config.allowUnfree = true;
networking.extraHosts = ''
${hostAddress} host.containers protonmail-bridge
'';
services = {
nextcloud = {
enable = true;
package = pkgs.nextcloud31;
# datadir = "/data";
database.createLocally = true;
hostName = "cloud.mjallen.dev";
appstoreEnable = true;
caching.redis = true;
configureRedis = true;
enableImagemagick = true;
https = true;
secretFile = secretsFile;
config = {
adminuser = "mjallen";
adminpassFile = adminpass;
dbhost = "localhost";
dbtype = "sqlite";
dbname = "nextcloud";
dbuser = "nextcloud";
};
settings = {
loglevel = 3;
allow_local_remote_servers = true;
upgrade.disable-web = false;
datadirectory = "/data";
trusted_domains = [
"${hostAddress}:${toString nextcloudPortExtHttp}"
"${hostAddress}:${toString nextcloudPortExtHttps}"
"${localAddress}:80"
"${localAddress}:443"
"cloud.mjallen.dev"
];
opcache.interned_strings_buffer = 16;
trusted_proxies = [ hostAddress ];
maintenance_window_start = 6;
default_phone_region = "US";
enable_previews = true;
enabledPreviewProviders = [
"OC\\Preview\\PNG"
"OC\\Preview\\JPEG"
"OC\\Preview\\GIF"
"OC\\Preview\\BMP"
"OC\\Preview\\XBitmap"
"OC\\Preview\\MP3"
"OC\\Preview\\TXT"
"OC\\Preview\\MarkDown"
"OC\\Preview\\OpenDocument"
"OC\\Preview\\Krita"
"OC\\Preview\\HEIC"
"OC\\Preview\\Movie"
"OC\\Preview\\MSOffice2003"
"OC\\Preview\\MSOffice2007"
"OC\\Preview\\MSOfficeDoc"
];
installed = true;
user_oidc = {
auto_provision = false;
soft_auto_provision = false;
allow_multiple_user_backends = false; # auto redirect to authentik for login
};
};
};
};
services.onlyoffice = {
enable = true;
port = onlyofficePortExt;
hostname = "office.mjallen.dev";
jwtSecretFile = jwtSecretFile;
};
# System packages
environment.systemPackages = with pkgs; [
cudaPackages.cudnn
cudatoolkit
ffmpeg
# libtensorflow-bin
nextcloud31
nodejs
onlyoffice-documentserver
sqlite
];
# Create required users and groups
users.users.nextcloud = {
isSystemUser = true;
uid = lib.mkForce nextcloudUserId;
group = "nextcloud";
};
users.users.onlyoffice = {
group = lib.mkForce "nextcloud";
};
users.groups = {
nextcloud = {
gid = lib.mkForce nextcloudGroupId;
};
downloads = { };
};
# Create and set permissions for required directories
system.activationScripts.nextcloud-dirs = ''
mkdir -p /data
chown -R nextcloud:nextcloud /data
chown -R nextcloud:nextcloud /run/secrets/jallen-nas/nextcloud
chmod -R 775 /data
chmod -R 750 /run/secrets/jallen-nas/nextcloud
'';
hardware = {
graphics = {
enable = true;
# setLdLibraryPath = true;
};
};
programs = {
nix-ld.enable = true;
};
system.stateVersion = "23.11";
networking = {
firewall = {
enable = true;
allowedTCPPorts = [
80
443
onlyofficePortExt
];
};
# Use systemd-resolved inside the container
# Workaround for bug https://github.com/NixOS/nixpkgs/issues/162686
useHostResolvConf = lib.mkForce false;
};
services.resolved.enable = true;
};
};
networking = {
nat = {
forwardPorts = [
{
destination = "${localAddress}:443";
sourcePort = nextcloudPortExtHttps;
}
{
destination = "${localAddress}:80";
sourcePort = nextcloudPortExtHttp;
}
{
destination = "${localAddress}:8000";
sourcePort = 8000;
}
{
destination = "${localAddress}:${toString onlyofficePortExt}";
sourcePort = onlyofficePortExt;
}
];
};
};
};
}

View File

@@ -0,0 +1,7 @@
{ lib, namespace, ... }:
with lib;
{
options.${namespace}.services.nextcloud = {
enable = mkEnableOption "enable nextcloud";
};
}

View File

@@ -0,0 +1,30 @@
{
lib,
config,
namespace,
...
}:
with lib;
let
cfg = config.${namespace}.services.orca-slicer;
in
{
imports = [ ./options.nix ];
config = mkIf cfg.enable {
virtualisation.oci-containers.containers."${cfg.name}" = {
autoStart = cfg.autoStart;
image = cfg.image;
ports = [
"${cfg.httpPort}:3000"
"${cfg.httpsPort}:3001"
];
volumes = [ "${cfg.configPath}:/config" ];
environment = {
PUID = cfg.puid;
PGID = cfg.pgid;
TZ = cfg.timeZone;
};
};
};
}

View File

@@ -0,0 +1,57 @@
{ lib, namespace, ... }:
with lib;
{
options.${namespace}.services.orca-slicer = {
enable = mkEnableOption "orca slicer docker service";
autoStart = mkOption {
type = types.bool;
default = true;
};
httpPort = mkOption {
type = types.str;
default = "3000";
};
httpsPort = mkOption {
type = types.str;
default = "3001";
};
name = mkOption {
type = types.str;
default = "orca-slicer";
};
image = mkOption {
type = types.str;
default = "linuxserver/orcaslicer";
};
configPath = mkOption {
type = types.str;
default = "/media/nas/main/ssd_app_data/orca-slicer";
};
dataPath = mkOption {
type = types.str;
default = "/media/nas/main/3d_printer";
};
puid = mkOption {
type = types.str;
default = "911";
};
pgid = mkOption {
type = types.str;
default = "1000";
};
timeZone = mkOption {
type = types.str;
default = "America/Chicago";
};
};
}

View File

@@ -0,0 +1,110 @@
{
config,
lib,
namespace,
...
}:
with lib;
let
cfg = config.${namespace}.services.paperless;
paperlessPort = 28981;
paperlessUserId = config.users.users.nix-apps.uid;
paperlessGroupId = config.users.groups.jallen-nas.gid;
paperlessPkg = pkgs.paperless-ngx;
in
{
imports = [ ./options.nix ];
config = mkIf cfg.enable {
containers.paperless = {
autoStart = true;
privateNetwork = true;
hostAddress = "10.0.1.3";
localAddress = "10.0.1.20";
hostAddress6 = "fc00::1";
localAddress6 = "fc00::20";
config =
{
lib,
...
}:
{
# Enable paperless service
services.paperless = {
enable = false;
package = paperlessPkg;
port = paperlessPort;
user = "paperless";
address = "0.0.0.0";
passwordFile = "/var/lib/paperless/paperless-password";
# environmentFile = paperlessEnv; # unstable is too unstable, but this doesnt exist in stable.... disabling altogether....
};
# Create required users and groups
users.groups = {
documents = {
gid = lib.mkForce paperlessGroupId;
};
};
users.users.paperless = {
isSystemUser = true;
uid = lib.mkForce paperlessUserId;
group = lib.mkForce "documents";
};
# Create and set permissions for required directories
system.activationScripts.paperless-dirs = ''
mkdir -p /var/lib/paperless
chown -R paperless:documents /var/lib/paperless
chmod -R 775 /var/lib/paperless
'';
networking = {
firewall = {
enable = true;
allowedTCPPorts = [ paperlessPort ];
};
# Use systemd-resolved inside the container
# Workaround for bug https://github.com/NixOS/nixpkgs/issues/162686
useHostResolvConf = lib.mkForce false;
};
services.resolved.enable = true;
system.stateVersion = "23.11";
};
# Bind mount directories from host
bindMounts = {
"/var/lib/paperless" = {
hostPath = "/media/nas/main/nix-app-data/paperless";
isReadOnly = false;
};
secrets = {
hostPath = "/run/secrets/jallen-nas/paperless";
isReadOnly = true;
mountPoint = "/run/secrets/jallen-nas/paperless";
};
secret-env = {
hostPath = "/run/secrets/rendered/paperless.env";
isReadOnly = true;
mountPoint = "/run/secrets/rendered/paperless.env";
};
};
};
networking.nat = {
forwardPorts = [
{
destination = "10.0.1.20:28981";
sourcePort = paperlessPort;
}
];
};
};
}

View File

@@ -0,0 +1,7 @@
{ lib, namespace, ... }:
with lib;
{
options.${namespace}.services.paperless = {
enable = mkEnableOption "enable paperless";
};
}

View File

@@ -0,0 +1,65 @@
{
config,
lib,
pkgs,
namespace,
...
}:
with lib;
let
cfg = config.${namespace}.services.protonmail-bridge;
in
{
imports = [ ./options.nix ];
config = mkIf cfg.enable {
# Open firewall for protonmail bridge if enabled
networking.firewall = mkIf cfg.openFirewall {
allowedTCPPorts = [
cfg.smtpPort
cfg.imapPort
];
allowedUDPPorts = [
cfg.smtpPort
cfg.imapPort
];
};
# Install protonmail-bridge package
environment.systemPackages = with pkgs; [
protonmail-bridge
gnome-keyring
gnupg
pass
];
# Configure systemd user service for protonmail-bridge
systemd.user.services.protonmail-bridge = {
description = "Protonmail Bridge";
enable = true;
environment = {
GNUPGHOME = "%h/.gnupg";
PASSWORD_STORE_DIR = "%h/.password-store";
};
script = "${pkgs.protonmail-bridge}/bin/protonmail-bridge --noninteractive";
path = with pkgs; [
gnome-keyring
gnupg
pass
protonmail-bridge
];
wantedBy = [ "default.target" ];
after = [ "gpg-agent.service" ];
};
# Enable gnome keyring for password storage
security.pam.services.login.enableGnomeKeyring = true;
services.gnome.gnome-keyring.enable = true;
# Configure gpg-agent
programs.gnupg.agent = {
enable = true;
enableSSHSupport = true;
};
};
}

View File

@@ -0,0 +1,31 @@
{ lib, namespace, ... }:
with lib;
{
options.${namespace}.services.protonmail-bridge = {
enable = mkEnableOption "protonmail bridge service";
smtpPort = mkOption {
type = types.port;
default = 1025;
description = "SMTP port for protonmail bridge";
};
imapPort = mkOption {
type = types.port;
default = 1143;
description = "IMAP port for protonmail bridge";
};
openFirewall = mkOption {
type = types.bool;
default = true;
description = "Whether to open firewall for protonmail bridge";
};
user = mkOption {
type = types.str;
default = "admin";
description = "User to run protonmail bridge as";
};
};
}

View File

@@ -0,0 +1,33 @@
{
config,
lib,
namespace,
...
}:
with lib;
let
cfg = config.${namespace}.services.restic;
in
{
imports = [ ./options.nix ];
config = mkIf cfg.enable {
# Configure the standard NixOS restic server service
services.restic.server = {
enable = true;
dataDir = cfg.dataDir;
prometheus = cfg.prometheus;
listenAddress = "${cfg.listenAddress}:${toString cfg.port}";
extraFlags = cfg.extraFlags;
}
// optionalAttrs (cfg.htpasswdFile != null) {
htpasswd-file = cfg.htpasswdFile;
};
# Open firewall for restic server if enabled
networking.firewall = mkIf cfg.openFirewall {
allowedTCPPorts = [ cfg.port ];
allowedUDPPorts = [ cfg.port ];
};
};
}

View File

@@ -0,0 +1,49 @@
{ lib, namespace, ... }:
with lib;
{
options.${namespace}.services.restic = {
enable = mkEnableOption "restic server with enhanced configuration";
port = mkOption {
type = types.port;
default = 8008;
description = "Port for restic server";
};
openFirewall = mkOption {
type = types.bool;
default = true;
description = "Whether to open firewall for restic server";
};
dataDir = mkOption {
type = types.str;
default = "/var/lib/restic";
description = "Data directory for restic server";
};
listenAddress = mkOption {
type = types.str;
default = "0.0.0.0";
description = "Address to bind restic server to";
};
prometheus = mkOption {
type = types.bool;
default = true;
description = "Whether to enable prometheus metrics";
};
htpasswdFile = mkOption {
type = types.nullOr types.str;
default = null;
description = "Path to htpasswd file for authentication";
};
extraFlags = mkOption {
type = types.listOf types.str;
default = [ ];
description = "Extra flags to pass to restic server";
};
};
}

View File

@@ -0,0 +1,58 @@
{ lib, config, ... }:
with lib;
let
cfg = config.nas-samba;
in
{
imports = [ ./options.nix ];
config = mkIf cfg.enable {
# make shares visible for Windows clients
services.samba-wsdd = {
enable = true;
openFirewall = true;
};
services.netatalk = {
enable = cfg.enableTimeMachine;
settings = {
time-machine = {
path = cfg.timeMachinePath;
"valid users" = "whoever";
"time machine" = cfg.enableTimeMachine;
};
};
};
networking.firewall.enable = true;
networking.firewall.allowPing = true;
services.samba = {
enable = true;
openFirewall = true;
nsswins = true;
nmbd.enable = true;
settings =
let
make =
name: share:
nameValuePair "${name}" {
path = share.sharePath;
public = if share.enableTimeMachine then "no" else "yes";
browseable = if share.browseable then "yes" else "no";
writable = "yes";
"force group" = "jallen-nas";
"read only" = if share.readOnly then "yes" else "no";
"guest ok" = if share.guestOk then "yes" else "no";
"create mask" = share.createMask;
"directory mask" = share.directoryMask;
"fruit:aapl" = if share.enableTimeMachine then "yes" else "no";
"fruit:time machine" = if share.enableTimeMachine then "yes" else "no";
"vfs objects" = "catia fruit streams_xattr";
"fruit:time machine max size" = share.timeMachineMaxSize;
};
in
mapAttrs' make cfg.shares;
};
};
}

View File

@@ -0,0 +1,70 @@
{ lib, ... }:
with lib;
{
options.nas-samba = {
enable = mkEnableOption "nas samba service";
autoStart = mkOption {
type = types.bool;
default = true;
};
enableTimeMachine = mkOption {
type = types.bool;
default = false;
};
timeMachinePath = mkOption {
type = types.str;
default = "";
};
hostsAllow = mkOption {
type = types.str;
default = "";
};
shares = mkOption {
type = types.attrsOf (
types.submodule {
options = {
public = mkOption {
type = types.bool;
default = false;
};
sharePath = mkOption {
type = types.str;
default = "";
};
readOnly = mkOption {
type = types.bool;
default = false;
};
browseable = mkOption {
type = types.bool;
default = true;
};
guestOk = mkOption {
type = types.bool;
default = true;
};
createMask = mkOption {
type = types.str;
default = "0774";
};
directoryMask = mkOption {
type = types.str;
default = "0775";
};
enableTimeMachine = mkOption {
type = types.bool;
default = false;
};
timeMachineMaxSize = mkOption {
type = types.str;
default = "0K";
};
};
}
);
default = { };
};
};
}

View File

@@ -0,0 +1,455 @@
{
config,
lib,
namespace,
...
}:
with lib;
let
cfg = config.${namespace}.services.traefik;
domain = "mjallen.dev";
serverIp = "10.0.1.3";
# Forward services
authUrl = "http://${serverIp}:9000/outpost.goauthentik.io";
actualUrl = "http://${config.containers.actual.localAddress}:${toString config.containers.actual.config.services.actual.settings.port}";
authentikUrl = "http://${serverIp}:9000";
cacheUrl = "http://${serverIp}:9012";
cloudUrl = "http://${config.containers.nextcloud.localAddress}:80";
giteaUrl = "http://${config.containers.gitea.localAddress}:${toString config.containers.gitea.config.services.gitea.settings.server.HTTP_PORT}";
hassUrl = "http://nuc-nixos.local:8123";
immichUrl = "http://${serverIp}:${toString config.services.immich.port}";
jellyfinUrl = "http://${serverIp}:8096";
jellyseerrUrl = "http://${config.containers.jellyseerr.localAddress}:${toString config.containers.jellyseerr.config.services.jellyseerr.port}";
lubeloggerUrl = "http://${serverIp}:6754";
onlyofficeUrl = "http://${config.containers.nextcloud.localAddress}:${toString config.containers.nextcloud.config.services.onlyoffice.port}";
openWebUIUrl = "http://${serverIp}:8888";
paperlessUrl = "http://${config.containers.paperless.localAddress}:${toString config.containers.paperless.config.services.paperless.port}";
# Plugins
traefikPlugins = {
bouncer = {
moduleName = "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin";
version = "v1.4.2";
};
geoblock = {
moduleName = "github.com/PascalMinder/geoblock";
version = "v0.2.5";
};
};
crowdsecAppsecHost = "${serverIp}:7422";
crowdsecLapiKeyFile = config.sops.secrets."jallen-nas/traefik/crowdsec-lapi-key".path;
# Ports
httpPort = 80;
httpsPort = 443;
traefikPort = 8080;
metricsPort = 8082;
forwardPorts = [
httpPort
httpsPort
traefikPort
metricsPort
];
# misc
letsEncryptEmail = "jalle008@proton.me";
dataDir = "/media/nas/main/nix-app-data/traefik";
authentikAddress = "http://${serverIp}:9000/outpost.goauthentik.io/auth/traefik";
in
{
imports = [ ./options.nix ];
config = mkIf cfg.enable {
sops = {
secrets = {
"jallen-nas/traefik/crowdsec-lapi-key" = {
sopsFile = (lib.snowfall.fs.get-file "secrets/nas-secrets.yaml");
owner = config.users.users.traefik.name;
group = config.users.users.traefik.group;
restartUnits = [ "traefik.service" ];
};
"jallen-nas/traefik/cloudflare-dns-api-token" = {
sopsFile = (lib.snowfall.fs.get-file "secrets/nas-secrets.yaml");
};
"jallen-nas/traefik/cloudflare-zone-api-token" = {
sopsFile = (lib.snowfall.fs.get-file "secrets/nas-secrets.yaml");
};
"jallen-nas/traefik/cloudflare-api-key" = {
sopsFile = (lib.snowfall.fs.get-file "secrets/nas-secrets.yaml");
};
"jallen-nas/traefik/cloudflare-email" = {
sopsFile = (lib.snowfall.fs.get-file "secrets/nas-secrets.yaml");
};
};
templates = {
"traefik.env" = {
content = ''
CLOUDFLARE_DNS_API_TOKEN = ${config.sops.placeholder."jallen-nas/traefik/cloudflare-dns-api-token"}
CLOUDFLARE_ZONE_API_TOKEN = ${
config.sops.placeholder."jallen-nas/traefik/cloudflare-zone-api-token"
}
CLOUDFLARE_API_KEY = ${config.sops.placeholder."jallen-nas/traefik/cloudflare-api-key"}
CLOUDFLARE_EMAIL = ${config.sops.placeholder."jallen-nas/traefik/cloudflare-email"}
'';
owner = config.users.users.traefik.name;
group = config.users.users.traefik.group;
restartUnits = [ "traefik.service" ];
};
};
};
networking.firewall = {
allowedTCPPorts = forwardPorts;
allowedUDPPorts = forwardPorts;
};
services.traefik = {
enable = true;
dataDir = dataDir;
group = "jallen-nas"; # group;
environmentFiles = [ "${config.services.traefik.dataDir}/traefik.env" ]; # todo: sops
staticConfigOptions = {
entryPoints = {
web = {
address = ":${toString httpPort}";
asDefault = true;
http.redirections.entrypoint = {
to = "websecure";
scheme = "https";
};
};
websecure = {
address = ":${toString httpsPort}";
asDefault = true;
http.tls.certResolver = "letsencrypt";
};
metrics = {
address = ":${toString metricsPort}"; # Port for metrics
};
};
log = {
level = "INFO";
};
metrics = {
prometheus = {
entryPoint = "metrics";
addEntryPointsLabels = true;
addServicesLabels = true;
buckets = [
0.1
0.3
1.2
5.0
]; # Response time buckets
};
};
certificatesResolvers.letsencrypt.acme = {
email = letsEncryptEmail;
storage = "${config.services.traefik.dataDir}/acme.json";
dnsChallenge = {
provider = "cloudflare";
resolvers = [
"1.1.1.1:53"
"8.8.8.8:53"
];
};
};
api.dashboard = true;
# Access the Traefik dashboard on <Traefik IP>:8080 of your server
api.insecure = true;
experimental = {
plugins = traefikPlugins;
};
};
dynamicConfigOptions = {
http = {
middlewares = {
authentik = {
forwardAuth = {
tls.insecureSkipVerify = true;
address = authentikAddress;
trustForwardHeader = true;
authResponseHeaders = [
"X-authentik-username"
"X-authentik-groups"
"X-authentik-email"
"X-authentik-name"
"X-authentik-uid"
"X-authentik-jwt"
"X-authentik-meta-jwks"
"X-authentik-meta-outpost"
"X-authentik-meta-provider"
"X-authentik-meta-app"
"X-authentik-meta-version"
];
};
};
onlyoffice-websocket = {
headers.customrequestheaders = {
X-Forwarded-Proto = "https";
};
};
crowdsec = {
plugin = {
bouncer = {
crowdsecAppsecEnabled = true;
crowdsecAppsecHost = crowdsecAppsecHost;
crowdsecAppsecFailureBlock = true;
crowdsecAppsecUnreachableBlock = true;
crowdsecLapiKeyFile = crowdsecLapiKeyFile;
};
};
};
whitelist-geoblock = {
plugin = {
geoblock = {
silentStartUp = false;
allowLocalRequests = true;
logLocalRequests = false;
logAllowedRequests = false;
logApiRequests = false;
api = "https://get.geojs.io/v1/ip/country/{ip}";
apiTimeoutMs = 500;
cacheSize = 25;
forceMonthlyUpdate = true;
allowUnknownCountries = false;
unknownCountryApiResponse = "nil";
blackListMode = false;
countries = [
"CA"
"US"
];
};
};
};
internal-ipallowlist = {
ipAllowList = {
sourceRange = [
"127.0.0.1/32"
"10.0.1.0/24"
];
};
};
};
services = {
auth.loadBalancer.servers = [
{
url = authUrl;
}
];
actual.loadBalancer.servers = [
{
url = actualUrl;
}
];
authentik.loadBalancer.servers = [
{
url = authentikUrl;
}
];
cache.loadBalancer.servers = [
{
url = cacheUrl;
}
];
chat.loadBalancer.servers = [
{
url = openWebUIUrl;
}
];
cloud.loadBalancer.servers = [
{
url = cloudUrl;
}
];
gitea.loadBalancer.servers = [
{
url = giteaUrl;
}
];
hass.loadBalancer.servers = [
{
url = hassUrl;
}
];
immich.loadBalancer.servers = [
{
url = immichUrl;
}
];
jellyfin.loadBalancer.servers = [
{
url = jellyfinUrl;
}
];
jellyseerr.loadBalancer.servers = [
{
url = jellyseerrUrl;
}
];
lubelogger.loadBalancer.servers = [
{
url = lubeloggerUrl;
}
];
onlyoffice.loadBalancer.servers = [
{
url = onlyofficeUrl;
}
];
paperless.loadBalancer.servers = [
{
url = paperlessUrl;
}
];
};
routers = {
auth = {
entryPoints = [ "websecure" ];
rule = "HostRegexp(`{subdomain:[a-z]+}.mjallen.dev`) && PathPrefix(`/outpost.goauthentik.io/`)";
service = "auth";
middlewares = [
"crowdsec"
"whitelist-geoblock"
];
priority = 15;
tls.certResolver = "letsencrypt";
};
actual = {
entryPoints = [ "websecure" ];
rule = "Host(`actual.${domain}`)";
service = "actual";
middlewares = [
"crowdsec"
"whitelist-geoblock"
];
tls.certResolver = "letsencrypt";
};
authentik = {
entryPoints = [ "websecure" ];
rule = "Host(`authentik.${domain}`)";
service = "authentik";
middlewares = [
"crowdsec"
"whitelist-geoblock"
];
tls.certResolver = "letsencrypt";
};
cache = {
entryPoints = [ "websecure" ];
rule = "Host(`cache.${domain}`)";
service = "cache";
middlewares = [
"crowdsec"
"whitelist-geoblock"
];
priority = 10;
tls.certResolver = "letsencrypt";
};
cloud = {
entryPoints = [ "websecure" ];
rule = "Host(`cloud.${domain}`)";
service = "cloud";
middlewares = [
"crowdsec"
"whitelist-geoblock"
];
tls.certResolver = "letsencrypt";
};
gitea = {
entryPoints = [ "websecure" ];
rule = "Host(`gitea.${domain}`)";
service = "gitea";
middlewares = [
"crowdsec"
"whitelist-geoblock"
];
tls.certResolver = "letsencrypt";
};
hass = {
entryPoints = [ "websecure" ];
rule = "Host(`hass.${domain}`)";
service = "hass";
middlewares = [
"crowdsec"
"whitelist-geoblock"
"authentik"
];
priority = 10;
tls.certResolver = "letsencrypt";
};
immich = {
entryPoints = [ "websecure" ];
rule = "Host(`immich.${domain}`)";
service = "immich";
middlewares = [
"crowdsec"
"whitelist-geoblock"
];
tls.certResolver = "letsencrypt";
};
jellyfin = {
entryPoints = [ "websecure" ];
rule = "Host(`jellyfin.${domain}`)";
service = "jellyfin";
middlewares = [
"crowdsec"
"whitelist-geoblock"
];
tls.certResolver = "letsencrypt";
};
jellyseerr = {
entryPoints = [ "websecure" ];
rule = "Host(`jellyseerr.${domain}`)";
service = "jellyseerr";
middlewares = [
"crowdsec"
"whitelist-geoblock"
];
tls.certResolver = "letsencrypt";
};
lubelogger = {
entryPoints = [ "websecure" ];
rule = "Host(`lubelogger.${domain}`)";
service = "lubelogger";
middlewares = [
"crowdsec"
"whitelist-geoblock"
];
tls.certResolver = "letsencrypt";
};
onlyoffice = {
entryPoints = [ "websecure" ];
rule = "Host(`office.${domain}`)";
service = "onlyoffice";
middlewares = [
"crowdsec"
"whitelist-geoblock"
"onlyoffice-websocket"
];
tls.certResolver = "letsencrypt";
};
};
};
};
};
};
}

View File

@@ -0,0 +1,7 @@
{ lib, namespace, ... }:
with lib;
{
options.${namespace}.services.traefik = {
enable = mkEnableOption "enable traefik";
};
}

View File

@@ -0,0 +1,32 @@
{
config,
lib,
namespace,
...
}:
with lib;
let
cfg = config.${namespace}.services.wyoming;
in
{
imports = [ ./options.nix ];
config = mkIf cfg.enable {
services.wyoming = {
faster-whisper.servers.hass-whisper = {
enable = true;
useTransformers = false;
device = lib.mkForce "cuda";
language = "en";
model = "distil-large-v3";
uri = "tcp://0.0.0.0:10300";
};
piper.servers.hass-piper = {
enable = true;
voice = "en-us-ryan-high";
uri = "tcp://0.0.0.0:10200";
};
};
};
}

View File

@@ -0,0 +1,7 @@
{ lib, namespace, ... }:
with lib;
{
options.${namespace}.services.wyoming = {
enable = mkEnableOption "enable wyoming";
};
}