Nix-Vibe public snapshot (squashed history)
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
{ config
|
||||
, pkgs
|
||||
, lib
|
||||
, ...
|
||||
}:
|
||||
|
||||
{
|
||||
options.services.backrest = {
|
||||
enable = lib.mkEnableOption "Backrest web UI for restic backup";
|
||||
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 9898;
|
||||
description = "Port for the Backrest web UI";
|
||||
};
|
||||
|
||||
host = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "127.0.0.1";
|
||||
description = "Address to bind Backrest to";
|
||||
};
|
||||
|
||||
dataDir = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
default = "/var/lib/backrest";
|
||||
description = "Directory for Backrest config and data";
|
||||
};
|
||||
|
||||
openFirewall = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Open the firewall for the Backrest web UI";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.services.backrest.enable {
|
||||
systemd.tmpfiles.rules = [
|
||||
"d ${config.services.backrest.dataDir} 0700 backrest backrest -"
|
||||
];
|
||||
|
||||
users.users.backrest = {
|
||||
isSystemUser = true;
|
||||
group = "backrest";
|
||||
home = config.services.backrest.dataDir;
|
||||
createHome = true;
|
||||
};
|
||||
|
||||
users.groups.backrest = { };
|
||||
|
||||
systemd.services.backrest = {
|
||||
description = "Backrest web UI for restic backup";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
User = "backrest";
|
||||
Group = "backrest";
|
||||
ExecStart = ''
|
||||
${pkgs.backrest}/bin/backrest \
|
||||
-bind-address ${config.services.backrest.host}:${toString config.services.backrest.port} \
|
||||
-data-dir ${config.services.backrest.dataDir} \
|
||||
-restic-cmd ${pkgs.restic}/bin/restic
|
||||
'';
|
||||
Restart = "on-failure";
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = lib.mkIf config.services.backrest.openFirewall [
|
||||
config.services.backrest.port
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
{ config
|
||||
, pkgs
|
||||
, lib
|
||||
, ...
|
||||
}:
|
||||
let
|
||||
cfg = config.services.homepage;
|
||||
in
|
||||
{
|
||||
options.services.homepage = {
|
||||
enable = lib.mkEnableOption "Homepage dashboard (gethomepage.dev)";
|
||||
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 8082;
|
||||
description = "Port for the Homepage dashboard";
|
||||
};
|
||||
|
||||
openFirewall = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Open the firewall for the Homepage dashboard";
|
||||
};
|
||||
|
||||
allowedHosts = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [
|
||||
"localhost"
|
||||
"127.0.0.1"
|
||||
];
|
||||
description = "Host header values Homepage is allowed to respond to";
|
||||
};
|
||||
|
||||
settings = lib.mkOption {
|
||||
type = lib.types.anything;
|
||||
default = { };
|
||||
description = "Homepage settings (settings.yaml). See https://gethomepage.dev/configs/settings/";
|
||||
};
|
||||
|
||||
services = lib.mkOption {
|
||||
type = lib.types.anything;
|
||||
default = [ ];
|
||||
description = "Homepage services (services.yaml). See https://gethomepage.dev/configs/services/";
|
||||
};
|
||||
|
||||
widgets = lib.mkOption {
|
||||
type = lib.types.anything;
|
||||
default = [ ];
|
||||
description = "Homepage widgets (widgets.yaml). See https://gethomepage.dev/configs/info-widgets/";
|
||||
};
|
||||
|
||||
bookmarks = lib.mkOption {
|
||||
type = lib.types.anything;
|
||||
default = [ ];
|
||||
description = "Homepage bookmarks (bookmarks.yaml). See https://gethomepage.dev/configs/bookmarks/";
|
||||
};
|
||||
|
||||
customCSS = lib.mkOption {
|
||||
type = lib.types.lines;
|
||||
default = "";
|
||||
description = "Custom CSS for Homepage";
|
||||
};
|
||||
|
||||
customJS = lib.mkOption {
|
||||
type = lib.types.lines;
|
||||
default = "";
|
||||
description = "Custom JavaScript for Homepage";
|
||||
};
|
||||
|
||||
environmentFiles = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.path;
|
||||
default = [ ];
|
||||
description = "Environment files passed to the Homepage service (for API keys etc.)";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
services.homepage-dashboard = {
|
||||
enable = true;
|
||||
listenPort = cfg.port;
|
||||
openFirewall = cfg.openFirewall;
|
||||
allowedHosts = lib.concatStringsSep "," (
|
||||
map (host: "${host}:${toString cfg.port}") cfg.allowedHosts
|
||||
);
|
||||
inherit (cfg)
|
||||
settings
|
||||
services
|
||||
widgets
|
||||
bookmarks
|
||||
customCSS
|
||||
customJS
|
||||
environmentFiles
|
||||
;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
{ config
|
||||
, pkgs
|
||||
, lib
|
||||
, ...
|
||||
}:
|
||||
|
||||
{
|
||||
options.services.immich-server = {
|
||||
enable = lib.mkEnableOption "Immich photo management server";
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 2283;
|
||||
description = "Port for Immich server";
|
||||
};
|
||||
mediaLocation = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
default = "/data/immich";
|
||||
description = "Directory for storing uploaded photos and videos";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.services.immich-server.enable {
|
||||
services.immich = {
|
||||
enable = true;
|
||||
host = "0.0.0.0";
|
||||
port = config.services.immich-server.port;
|
||||
mediaLocation = config.services.immich-server.mediaLocation;
|
||||
# Firewall is managed by the host (homeserver-1 restricts to tailscale).
|
||||
openFirewall = false;
|
||||
# Restrict hardware-acceleration access to the explicit NVIDIA device
|
||||
# nodes needed for NVENC/CUDA transcoding (GTX 960). null would grant
|
||||
# access to ALL devices; an explicit list is the secure alternative.
|
||||
accelerationDevices = [
|
||||
"/dev/nvidia0"
|
||||
"/dev/nvidiactl"
|
||||
"/dev/nvidia-modeset"
|
||||
"/dev/nvidia-uvm"
|
||||
"/dev/nvidia-uvm-tools"
|
||||
];
|
||||
};
|
||||
|
||||
# The upstream services.immich module creates a tmpfiles `e` rule that
|
||||
# sets the media directory to 0700 on every rebuild/switch. Setting that
|
||||
# mode clobbers the POSIX ACL mask (to `---`), which in turn nullifies the
|
||||
# `user:backrest` read ACL that lets the backup service read this directory.
|
||||
# Override it to keep the directory group-readable/traversable so the ACL
|
||||
# mask stays effective (matches the rule just above).
|
||||
systemd.tmpfiles.settings.immich.${config.services.immich-server.mediaLocation}.e.mode =
|
||||
lib.mkForce "0770";
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d ${config.services.immich-server.mediaLocation} 0770 immich immich -"
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
{ config
|
||||
, pkgs
|
||||
, lib
|
||||
, ...
|
||||
}:
|
||||
let
|
||||
# Jellyfin SSO plugin built from upstream release zip.
|
||||
# Not in nixpkgs, so we package it here for use in a bind mount.
|
||||
ssoPlugin = pkgs.stdenvNoCC.mkDerivation {
|
||||
pname = "jellyfin-plugin-sso";
|
||||
version = "4.0.0.4";
|
||||
src = pkgs.fetchzip {
|
||||
url = "https://github.com/9p4/jellyfin-plugin-sso/releases/download/v4.0.0.4/sso-authentication_4.0.0.4.zip";
|
||||
hash = "sha256-MJTyE6CeVLk7mlugauJ/F6bpi1kYwNtzNmQeH3+CFeQ=";
|
||||
stripRoot = false;
|
||||
};
|
||||
dontBuild = true;
|
||||
installPhase = ''
|
||||
mkdir -p $out
|
||||
cp -r ./* $out/
|
||||
'';
|
||||
};
|
||||
|
||||
# Plugin directory name expected by Jellyfin (version-specific).
|
||||
pluginDirName = "SSO-Auth_4.0.0.4";
|
||||
in
|
||||
{
|
||||
options.services.jellyfin-server = {
|
||||
enable = lib.mkEnableOption "Jellyfin media server";
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 8096;
|
||||
description = "Port for Jellyfin web interface";
|
||||
};
|
||||
mediaLocation = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
default = "/data/jellyfin";
|
||||
description = "Directory for storing media files";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.services.jellyfin-server.enable {
|
||||
services.jellyfin = {
|
||||
enable = true;
|
||||
openFirewall = true;
|
||||
group = "jellyfin";
|
||||
# Hardware acceleration for NVIDIA NVENC transcoding (GTX 960 on homeserver-1).
|
||||
# Requires the legacy_580 driver branch for Maxwell GPUs.
|
||||
hardwareAcceleration = {
|
||||
enable = true;
|
||||
type = "nvenc";
|
||||
device = "/dev/nvidia0";
|
||||
};
|
||||
# Force encoding config so NVENC settings are applied on every restart.
|
||||
# Without this, changes made in the web UI persist, but NixOS settings are ignored.
|
||||
forceEncodingConfig = true;
|
||||
transcoding = {
|
||||
# Enable hardware encoding for H.264 and H.265 (HEVC).
|
||||
enableHardwareEncoding = true;
|
||||
# Optional: enable tone mapping for HDR → SDR conversion.
|
||||
enableToneMapping = false;
|
||||
};
|
||||
};
|
||||
|
||||
# Media dir: petere writes via Samba (forced to group jellyfin), Jellyfin
|
||||
# reads as group jellyfin. Not world-writable.
|
||||
# Also ensure the plugins directory exists for the SSO plugin bind mount.
|
||||
systemd.tmpfiles.rules = [
|
||||
"d ${config.services.jellyfin-server.mediaLocation} 0775 root jellyfin -"
|
||||
"d /var/lib/jellyfin/plugins 0755 jellyfin jellyfin -"
|
||||
];
|
||||
|
||||
# Samba share is authenticated and restricted to petere (the only writer).
|
||||
# No guest access; the Samba password is managed via SOPS (see
|
||||
# samba-petere-password secret and the samba-set-password service below).
|
||||
services.samba = {
|
||||
enable = true;
|
||||
openFirewall = true;
|
||||
settings = {
|
||||
global = {
|
||||
"server role" = "standalone server";
|
||||
security = "user";
|
||||
};
|
||||
jellyfin = {
|
||||
path = "${config.services.jellyfin-server.mediaLocation}";
|
||||
browseable = "yes";
|
||||
"read only" = "no";
|
||||
"guest ok" = "no";
|
||||
"valid users" = "petere";
|
||||
"force group" = "jellyfin";
|
||||
"force create mode" = "0664";
|
||||
"force directory mode" = "0775";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# Install the SSO plugin by copying from the Nix store.
|
||||
# Jellyfin needs to write plugin metadata (meta.json), so we can't use
|
||||
# a read-only bind mount from /nix/store. This oneshot service is
|
||||
# idempotent: it only copies if the plugin dir doesn't exist or differs.
|
||||
systemd.services.jellyfin-install-plugin-sso = {
|
||||
description = "Install Jellyfin SSO Auth plugin";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
before = [ "jellyfin.service" ];
|
||||
serviceConfig.Type = "oneshot";
|
||||
script = ''
|
||||
PLUGIN_DIR="/var/lib/jellyfin/plugins/${pluginDirName}"
|
||||
SOURCE="${ssoPlugin}"
|
||||
|
||||
# Only copy if the plugin doesn't exist or source changed
|
||||
if [ ! -d "$PLUGIN_DIR" ] || ! diff -rq "$SOURCE" "$PLUGIN_DIR" >/dev/null 2>&1; then
|
||||
rm -rf "$PLUGIN_DIR"
|
||||
cp -r "$SOURCE" "$PLUGIN_DIR"
|
||||
chown -R jellyfin:jellyfin "$PLUGIN_DIR"
|
||||
chmod -R u+w "$PLUGIN_DIR"
|
||||
fi
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.services.jellyfin = {
|
||||
after = [ "jellyfin-install-plugin-sso.service" ];
|
||||
wants = [ "jellyfin-install-plugin-sso.service" ];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
{ config
|
||||
, pkgs
|
||||
, lib
|
||||
, ...
|
||||
}:
|
||||
|
||||
{
|
||||
options.services.ntfy-container = {
|
||||
enable = lib.mkEnableOption "ntfy service";
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 8080;
|
||||
description = "Host port to map to ntfy";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.services.ntfy-container.enable {
|
||||
services.ntfy-sh = {
|
||||
enable = true;
|
||||
settings = {
|
||||
base-url = "https://ntfy.edley.me";
|
||||
# Bind to all interfaces - nginx proxy (in Docker container) needs to reach it.
|
||||
# Security: firewall restricts access to nginx proxy machine only.
|
||||
listen-http = ":${toString config.services.ntfy-container.port}";
|
||||
cache-file = "/var/lib/ntfy-sh/cache.db";
|
||||
auth-file = "/var/lib/ntfy-sh/user.db";
|
||||
behind-proxy = true; # Explicitly enable proxy support (X-Forwarded-*)
|
||||
|
||||
# Security: Deny all access by default, require login
|
||||
auth-default-access = "deny-all";
|
||||
enable-signup = false;
|
||||
enable-login = true;
|
||||
# Require login for ALL web app actions - hides the public UI
|
||||
# behind authentication so anonymous visitors can't even browse
|
||||
# the web app.
|
||||
require-login = true;
|
||||
};
|
||||
};
|
||||
|
||||
# Firewall: allow access only from nginx proxy machine (adjust IP as needed).
|
||||
# Example: allow from 10.0.0.x (Tailscale IP of nginx proxy machine)
|
||||
# networking.firewall.allowedTCPPorts = [ config.services.ntfy-container.port ];
|
||||
# networking.firewall.interfaces.tailscale0.allowedTCPPorts = [ config.services.ntfy-container.port ];
|
||||
|
||||
# Ensure state directory exists (ntfy-sh service usually creates it, but good to ensure permissions if needed)
|
||||
# The systemd service for ntfy-sh usually handles DynamicUser/StateDirectory or similar.
|
||||
# We can trust the upstream module for basic setup.
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
{ config
|
||||
, pkgs
|
||||
, lib
|
||||
, ...
|
||||
}:
|
||||
|
||||
{
|
||||
options.services.paperless-service = {
|
||||
enable = lib.mkEnableOption "Paperless-ngx service";
|
||||
passwordFile = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
default = "/etc/nixos/paperless-password";
|
||||
description = "Path to the file containing the admin password";
|
||||
};
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 28981;
|
||||
description = "Port to listen on";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.services.paperless-service.enable {
|
||||
services.paperless = {
|
||||
enable = true;
|
||||
passwordFile = config.services.paperless-service.passwordFile;
|
||||
# Listening on 0.0.0.0 but the firewall only opens the port on tailscale0,
|
||||
# so Paperless is reachable over Tailscale only (not the LAN).
|
||||
address = "0.0.0.0";
|
||||
port = config.services.paperless-service.port;
|
||||
settings = {
|
||||
PAPERLESS_OCR_LANGUAGE = "eng";
|
||||
# Set the URL to the Tailscale hostname
|
||||
PAPERLESS_URL = "http://x1carbon:${toString config.services.paperless-service.port}";
|
||||
# Allow requests from the Tailscale hostname
|
||||
PAPERLESS_ALLOWED_HOSTS = "x1carbon,x1carbon.tailscale.net";
|
||||
};
|
||||
};
|
||||
|
||||
# Tailscale-only access: open the port on the tailscale0 interface only.
|
||||
networking.firewall.interfaces.tailscale0.allowedTCPPorts = [
|
||||
config.services.paperless-service.port
|
||||
];
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user