nixfiles/hosts/rpi4/services.nix

557 lines
16 KiB
Nix

{
config,
lib,
pkgs,
...
}: let
inherit (config.age) secrets;
inherit (builtins) toString;
in {
imports = [
./gitea.nix
./authelia.nix
];
config = {
age.secrets.cloudflaredns = {
file = ../../secrets/cloudflare-dns.age;
group = "secrets";
};
age.secrets.htpasswd-cam = {
file = ../../secrets/htpasswd-cam.age;
group = "nginx";
mode = "0750";
};
age.secrets.htpasswd = {
file = ../../secrets/htpasswd.age;
group = "nginx";
mode = "0750";
};
age.secrets.authelia-users = {
file = ../../secrets/authelia-users.age;
group = "authelia-shared";
mode = "0750";
};
age.secrets.authelia-jwt = {
file = ../../secrets/authelia-jwt.age;
group = "authelia-shared";
mode = "0750";
};
age.secrets.authelia-storage = {
file = ../../secrets/authelia-storage.age;
group = "authelia-shared";
mode = "0750";
};
age.secrets.authelia-session = {
file = ../../secrets/authelia-session.age;
group = "authelia-shared";
mode = "0750";
};
age.secrets.anki = {
file = ../../secrets/anki-user.age;
};
age.secrets.homepage = {
file = ../../secrets/homepage.age;
};
age.secrets.paperless-admin = {
file = ../../secrets/paperless-admin.age;
};
users.groups.secrets = {};
users.users.acme.extraGroups = ["secrets"];
security.acme = {
acceptTerms = true;
maxConcurrentRenewals = 1;
defaults.email = "iancoguz@gmail.com";
certs = {
"protogen.io" = {
credentialFiles = {
"CLOUDFLARE_EMAIL_FILE" = pkgs.writeText "email" "iancoguz@gmail.com";
"CLOUDFLARE_API_KEY_FILE" = config.age.secrets.cloudflaredns.path;
};
dnsProvider = "cloudflare";
domain = "protogen.io";
extraDomainNames = [
"*.protogen.io"
"nullbite.com"
"*.nullbite.com"
"nullbite.dev"
"*.nullbite.dev"
"nbt.sh"
"*.nbt.sh"
"proot.link"
"*.proot.link"
"nullbite.xyz"
"*.nullbite.xyz"
];
};
};
};
users.users.nginx.extraGroups = ["acme"];
networking.firewall.allowedTCPPorts = [
80
443
# this is needed for node to work for some reason
8123
];
users.groups.authelia-shared = {};
services.authelia.instances =
lib.mapAttrs (inst: opts: {
enable = true;
group = "authelia-shared";
secrets = {
jwtSecretFile = config.age.secrets.authelia-jwt.path;
storageEncryptionKeyFile = config.age.secrets.authelia-storage.path;
sessionSecretFile = config.age.secrets.authelia-session.path;
};
settings = {
access_control.default_policy = "one_factor";
storage.local.path = "/var/lib/authelia-${inst}/db.sqlite";
session.cookies = [
{
domain = "protogen.io";
authelia_url = "https://auth.protogen.io";
default_redirection_url = "https://searx.protogen.io";
}
{
domain = "nbt.sh";
authelia_url = "https://auth.nbt.sh";
default_redirection_url = "https://admin.nbt.sh";
}
{
domain = "proot.link";
authelia_url = "https://auth.proot.link";
default_redirection_url = "https://admin.proot.link";
}
];
session.redis = {
host = config.services.redis.servers.authelia.unixSocket;
};
notifier.filesystem.filename = "/var/lib/authelia-${inst}/notification.txt";
authentication_backend.file.path = config.age.secrets.authelia-users.path;
server.port = lib.mkIf (opts ? port) (opts.port or null);
theme = "auto";
};
}) {
main = {
domain = "protogen.io";
# port = 9091 # default
};
};
services.redis = {
servers.authelia = {
enable = true;
};
};
users.users."${config.services.authelia.instances.main.user}".extraGroups = let
name = config.services.redis.servers.authelia.user;
in [name];
services.nginx = {
enable = true;
recommendedProxySettings = true;
recommendedTlsSettings = true;
recommendedOptimisation = true;
commonHttpConfig = ''
port_in_redirect off;
'';
virtualHosts = let
useACMEHost = "protogen.io";
mkProxy = args @ {
upstream ? "http://127.0.0.1:${builtins.toString args.port}",
auth ? false,
authelia ? false,
extraConfig ? {},
...
}:
lib.mkMerge [
{
inherit useACMEHost;
forceSSL = true;
locations."/" = {
proxyPass = upstream;
proxyWebsockets = true;
};
}
(lib.mkIf auth {
basicAuthFile = config.age.secrets.htpasswd.path;
})
(lib.mkIf authelia {
authelia.instance = lib.mkDefault "main";
})
extraConfig
];
# mkReverseProxy = port: {
# inherit useACMEHost;
# forceSSL = true;
# locations."/" = {
# proxyPass = "http://127.0.0.1:${builtins.toString port}";
# proxyWebsockets = true;
# };
# };
mkAuthProxy = port:
mkProxy {
inherit port;
authelia = true;
};
mkReverseProxy = port: mkProxy {inherit port;};
in
(lib.mapAttrs (domain: instance: {
forceSSL = true;
inherit useACMEHost;
authelia.endpoint = {inherit instance;};
}) {
"auth.protogen.io" = "main";
"auth.nbt.sh" = "main";
"auth.proot.link" = "main";
})
// {
"changedetection.protogen.io" = mkReverseProxy 5000;
# firefly
"firefly.protogen.io" = mkReverseProxy 8083;
"firefly-import.protogen.io" = mkAuthProxy 8084;
"gitea.protogen.io" = mkReverseProxy 3000;
# home assistant
"hass.protogen.io" = mkReverseProxy 8123;
"node.protogen.io" = mkReverseProxy 1880;
"z2m.protogen.io" = mkAuthProxy 8124;
"vsc-hass.protogen.io" = mkReverseProxy 1881;
# jellyfin
"room.protogen.io" = mkReverseProxy 8096;
"deemix.protogen.io" = mkAuthProxy 6595;
# libreddit auth 8087
"libreddit.protogen.io" = {
locations."/".return = "302 https://redlib.protogen.io$request_uri";
forceSSL = true;
useACMEHost = "protogen.io";
};
"redlib.protogen.io" = mkAuthProxy 8087;
"rss.protogen.io" = mkReverseProxy 8082;
"blahaj.protogen.io" = mkReverseProxy 8086;
"paper.protogen.io" = mkReverseProxy config.services.paperless.port;
# octoprint (proxy_addr is 10.10.1.8)
"print.protogen.io" = lib.mkMerge [
(mkProxy {
authelia = true;
upstream = "http://10.10.1.8:80";
})
{
locations."/webcam" = {
proxyPass = "http://10.10.1.8:80$request_uri";
proxyWebsockets = true;
basicAuthFile = config.age.secrets.htpasswd-cam.path;
authelia.method = null;
};
}
];
# searx auth 8088 (none for /favicon.ico, /autocompleter, /opensearch.xml)
"search.protogen.io".locations."/".return = "302 https://searx.protogen.io$request_uri";
"searx.protogen.io" = let
port = 8088;
in
mkProxy {
authelia = true;
inherit port;
extraConfig = {
locations = lib.genAttrs ["/favicon.ico" "/autocompleter" "/opensearch.xml"] (attr: {
proxyPass = "http://localhost:${builtins.toString port}";
proxyWebsockets = true;
authelia.method = null;
extraConfig = ''
auth_basic off;
'';
});
};
};
# URL shortener
"nbt.sh" = mkProxy {
port = 8090;
extraConfig.serverAliases = ["proot.link"];
};
"admin.nbt.sh" = mkProxy {
authelia = true;
port = 8091;
extraConfig.serverAliases = ["admin.proot.link"];
};
# uptime
"uptime.protogen.io" = mkReverseProxy 3001;
"kuma.protogen.io".locations."/".return = "301 https://uptime.protogen.io";
"anki.protogen.io" = mkReverseProxy config.services.anki-sync-server.port;
# homepage
"home.protogen.io" = mkAuthProxy 8089;
"lounge.protogen.io" = mkAuthProxy 9000;
"trackmap.protogen.io" = let
root = pkgs.modpacks.notlite-ctm-static;
in {
useACMEHost = "protogen.io";
forceSSL = true;
authelia.instance = "main";
locations."/" = {
inherit root;
extraConfig = ''
autoindex off;
'';
};
locations."/api/" = {
proxyPass = "http://10.10.0.3:3876";
proxyWebsockets = true;
extraConfig = ''
chunked_transfer_encoding off;
proxy_buffering off;
proxy_cache off;
'';
};
};
# main site
"protogen.io" = {
serverAliases = ["x.protogen.io"];
useACMEHost = "protogen.io";
forceSSL = true;
locations."/" = {
root = "/srv/http";
extraConfig = ''
autoindex on;
'';
};
};
# fallback for known hosts
"nullbite.com" = {
forceSSL = true;
useACMEHost = "protogen.io";
locations."/" = {
return = "302 https://protogen.io$request_uri";
};
serverAliases = ["www.nullbite.com" "nullbite.dev" "www.nullbite.dev" "www.protogen.io" "nullbite.xyz" "www.nullbite.xyz"];
};
# show blank page for unknown hosts
"localhost" = {
default = true;
addSSL = true;
useACMEHost = "protogen.io";
locations."/" = {
return = "404";
};
};
};
};
# https://gethomepage.dev
services.homepage-dashboard = let
entry = name: value: {"${name}" = value;};
makeBookmark = name: {...} @ attrs: entry name [attrs];
makeBookmark' = name: icon: abbr: href: makeBookmark name ({inherit abbr href;} // lib.optionalAttrs (icon != null) {inherit icon;});
in {
enable = true;
listenPort = 8089;
environmentFile = secrets.homepage.path;
# bookmarks customCSS customJS docker environmentFile kubernetes services settings widgets
settings = {
theme = "dark";
color = "slate";
};
widgets = [
(entry "resources" {
cpu = true;
memory = true;
disk = "/";
})
(entry "search" {
provider = "custom";
target = "_self";
url = "https://searx.protogen.io/search?q=";
suggestionUrl = "https://searx.protogen.io/autocompleter?q=";
showSearchSuggestions = true;
})
];
services = let
service = name: subdomain: icon: {...} @ attrs:
entry name ({
href = "https://${subdomain}.protogen.io";
inherit icon;
}
// attrs);
basicService = name: subdomain: icon: service name subdomain icon {};
in [
(entry "unsorted" [
(basicService "Firefly III" "firefly" "firefly-iii")
(basicService "Gitea" "gitea" "gitea")
(basicService "Home Assistant" "hass" "home-assistant")
(basicService "Node-RED" "node" "node-red")
(basicService "Zigbee2MQTT" "z2m" "zigbee2mqtt")
(basicService "Code Server (Home Assistant)" "vsc-hass" "vscode")
(basicService "Deemix" "deemix" "deemix")
(basicService "Miniflux" "rss" "miniflux")
(basicService "mlmym" "blahaj" "lemmy-light")
(basicService "Octoprint" "print" "octoprint")
(basicService "SearXNG" "searx" "searxng")
(basicService "TheLounge" "lounge" "thelounge")
(basicService "Paperless" "paper" "paperless-ngx")
(entry "Shlink" {
href = "https://admin.nbt.sh";
icon = "shlink";
})
(basicService "Create Track Map" "trackmap" "")
((x:
service x x x {
widget = {
};
}) "changedetection")
(service "Uptime Kuma" "uptime" "uptime-kuma" {
widget = {
type = "uptimekuma";
url = "https://uptime.protogen.io";
slug = "all";
};
})
(service "Jellyfin" "room" "jellyfin" {
widget = {
type = "jellyfin";
url = "https://room.protogen.io";
key = "{{HOMEPAGE_VAR_JELLYFIN}}";
enableBlocks = true;
enableNowPlaying = true;
enableUser = true;
};
})
])
];
bookmarks = [
(entry "Admin" [
(makeBookmark' "Backblaze" "backblaze" "BZ" "https://secure.backblaze.com/user_signin.htm")
(makeBookmark' "Cloudflare" "cloudflare" "CF" "https://dash.cloudflare.com")
(makeBookmark' "Porkbun" "porkbun" "PB" "https://porkbun.com/account/domainsSpeedy")
(makeBookmark' "Namecheap" "namechea" "NC" "https://ap.www.namecheap.com/")
])
(entry "Developer" [
(makeBookmark' "GitHub" "github" "GH" "https://github.com")
])
(entry "Local" [
(makeBookmark' "Syncthing" "syncthing" "ST" "http://127.0.0.1:8384")
(makeBookmark' "Iris" null "IR" "http://localhost:6680/iris/")
])
(entry "Entertainment" [
(makeBookmark' "Redlib" "redlib" "RL" "https://redlib.protogen.io")
])
];
};
virtualisation.docker = {
enable = true;
storageDriver = "btrfs";
};
# needed for mDNS in Home Assistant
networking.firewall.allowedUDPPorts = [5353];
systemd.services.redlib.environment = {
REDLIB_DEFAULT_SUBSCRIPTIONS = lib.pipe ./reddit-subscriptions.txt [
builtins.readFile
(lib.splitString "\n")
(lib.filter (x: x != ""))
(lib.concatStringsSep "+")
];
REDLIB_DEFAULT_SHOW_NSFW = "on";
REDLIB_DEFAULT_BLUR_NSFW = "on";
REDLIB_DEFAULT_BLUR_SPOILER = "on";
REDLIB_DEFAULT_USE_HLS = "on";
REDLIB_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION = "on";
REDLIB_ENABLE_RSS = "on";
};
services.redlib = {
enable = true;
port = 8087;
};
services.thelounge = {
enable = true;
extraConfig = {
prefetch = true;
prefetchStorage = true;
prefetchMaxImageSize = 8192;
};
};
services.redis.servers.paperless.enable = true;
services.paperless = {
enable = true;
# default is "localhost", binding should not rely on DNS (even if
# localhost is hard-coded 99.999% of the time)
address = "127.0.0.1";
passwordFile = secrets.paperless-admin.path;
settings = {
PAPERLESS_ADMIN_USER = "nullbite";
PAPERLESS_REDIS = "unix://${config.services.redis.servers.paperless.unixSocket}";
PAPERLESS_URL = "https://paper.protogen.io";
PAPERLESS_TIKA_ENABLED = true;
PAPERLESS_TIKA_ENDPOINT = "http://localhost:${toString config.services.tika.port}";
PAPERLESS_TIKA_GOTENBERG_ENDPOINT = "http://localhost:${toString config.services.gotenberg.port}";
};
};
users.users."${config.services.paperless.user}".extraGroups = let
name = config.services.redis.servers.paperless.group;
in [name];
services.gotenberg = {
enable = true;
port = 3002;
};
services.tika.enable = true;
services.anki-sync-server = {
enable = true;
address = "127.0.0.1";
users = [
{
username = "nullbite";
passwordFile = config.age.secrets.anki.path;
}
];
};
};
}