add more services documentation

This commit is contained in:
Noah Masur 2024-01-09 23:11:11 -05:00
parent 984a47d3b1
commit dab40e081a
28 changed files with 179 additions and 9 deletions

View File

@ -2,6 +2,7 @@
let let
# This config specifies ports for Prometheus to scrape information
arrConfig = { arrConfig = {
radarr = { radarr = {
exportarrPort = "9707"; exportarrPort = "9707";
@ -41,6 +42,8 @@ in {
sabnzbd = { sabnzbd = {
enable = true; enable = true;
group = "media"; group = "media";
# The config file must be editable within the application
# It contains server configs and credentials
configFile = "/data/downloads/sabnzbd/sabnzbd.ini"; configFile = "/data/downloads/sabnzbd/sabnzbd.ini";
}; };
sonarr = { sonarr = {
@ -53,16 +56,23 @@ in {
}; };
}; };
# Create a media group to be shared between services
users.groups.media = { }; users.groups.media = { };
# Give the human user access to the media group
users.users.${config.user}.extraGroups = [ "media" ]; users.users.${config.user}.extraGroups = [ "media" ];
# Allows media group to read/write the sabnzbd directory
users.users.sabnzbd.homeMode = "0770"; users.users.sabnzbd.homeMode = "0770";
unfreePackages = [ "unrar" ]; # Required for sabnzbd unfreePackages = [ "unrar" ]; # Required as a dependency for sabnzbd
# Requires updating the base_url config value in each service # Requires updating the base_url config value in each service
# If you try to rewrite the URL, the service won't redirect properly # If you try to rewrite the URL, the service won't redirect properly
caddy.routes = [ caddy.routes = [
{ {
# Group means that routes with the same name are mutually exclusive,
# so they are split between the appropriate services.
group = "download"; group = "download";
match = [{ match = [{
host = [ config.hostnames.download ]; host = [ config.hostnames.download ];
@ -70,6 +80,7 @@ in {
}]; }];
handle = [{ handle = [{
handler = "reverse_proxy"; handler = "reverse_proxy";
# We're able to reference the url and port of the service dynamically
upstreams = [{ dial = arrConfig.sonarr.url; }]; upstreams = [{ dial = arrConfig.sonarr.url; }];
}]; }];
} }
@ -92,6 +103,7 @@ in {
}]; }];
handle = [{ handle = [{
handler = "reverse_proxy"; handler = "reverse_proxy";
# Prowlarr doesn't offer a dynamic config, so we have to hardcode it
upstreams = [{ dial = "localhost:9696"; }]; upstreams = [{ dial = "localhost:9696"; }];
}]; }];
} }
@ -104,6 +116,7 @@ in {
handle = [{ handle = [{
handler = "reverse_proxy"; handler = "reverse_proxy";
upstreams = [{ upstreams = [{
# Bazarr only dynamically sets the port, not the host
dial = "localhost:${ dial = "localhost:${
builtins.toString config.services.bazarr.listenPort builtins.toString config.services.bazarr.listenPort
}"; }";
@ -145,10 +158,12 @@ in {
Type = "simple"; Type = "simple";
DynamicUser = true; DynamicUser = true;
ExecStart = let ExecStart = let
# Sabnzbd doesn't accept the URI path, unlike the others
url = if name != "sabnzbd" then url = if name != "sabnzbd" then
"http://${attrs.url}/${name}" "http://${attrs.url}/${name}"
else else
"http://${attrs.url}"; "http://${attrs.url}";
# Exportarr is trained to pull from the arr services
in '' in ''
${pkgs.exportarr}/bin/exportarr ${name} \ ${pkgs.exportarr}/bin/exportarr ${name} \
--url ${url} \ --url ${url} \
@ -197,7 +212,7 @@ in {
prefix = "API_KEY="; prefix = "API_KEY=";
}; };
# Prometheus scrape targets # Prometheus scrape targets (expose Exportarr to Prometheus)
prometheus.scrapeTargets = map (key: prometheus.scrapeTargets = map (key:
"127.0.0.1:${ "127.0.0.1:${
lib.attrsets.getAttrFromPath [ key "exportarrPort" ] arrConfig lib.attrsets.getAttrFromPath [ key "exportarrPort" ] arrConfig

View File

@ -1,3 +1,6 @@
# This is my setup for backing up SQlite databases and other systems to S3 or
# S3-equivalent services (like Backblaze B2).
{ config, lib, ... }: { { config, lib, ... }: {
options = { options = {

View File

@ -1,3 +1,10 @@
# Bind is a DNS service. This allows me to resolve public domains locally so
# when I'm at home, I don't have to travel over the Internet to reach my
# server.
# To set this on all home machines, I point my router's DNS resolver to the
# local IP address of the machine running this service (swan).
{ config, pkgs, lib, ... }: { config, pkgs, lib, ... }:
let let
@ -16,11 +23,19 @@ in {
config = lib.mkIf config.services.bind.enable { config = lib.mkIf config.services.bind.enable {
# Normally I block all requests not coming from Cloudflare, so I have to also
# allow my local network.
caddy.cidrAllowlist = [ "192.168.0.0/16" ]; caddy.cidrAllowlist = [ "192.168.0.0/16" ];
services.bind = { services.bind = {
# Allow requests coming from these IPs. This way I don't somehow get
# spammed with DNS requests coming from the Internet.
cacheNetworks = [ "127.0.0.0/24" "192.168.0.0/16" ]; cacheNetworks = [ "127.0.0.0/24" "192.168.0.0/16" ];
# When making normal DNS requests, forward them to Cloudflare to resolve.
forwarders = [ "1.1.1.1" "1.0.0.1" ]; forwarders = [ "1.1.1.1" "1.0.0.1" ];
ipv4Only = true; ipv4Only = true;
# Use rpz zone as an override # Use rpz zone as an override
@ -47,6 +62,7 @@ in {
}; };
# We must allow DNS traffic to hit our machine as well
networking.firewall.allowedTCPPorts = [ 53 ]; networking.firewall.allowedTCPPorts = [ 53 ];
networking.firewall.allowedUDPPorts = [ 53 ]; networking.firewall.allowedUDPPorts = [ 53 ];

View File

@ -1,3 +1,14 @@
# Caddy is a reverse proxy, like Nginx or Traefik. This creates an ingress
# point from my local network or the public (via Cloudflare). Instead of a
# Caddyfile, I'm using the more expressive JSON config file format. This means
# I can source routes from other areas in my config and build the JSON file
# using the result of the expression.
# Caddy helpfully provides automatic ACME cert generation and management, but
# it requires a form of validation. We are using a custom build of Caddy
# (compiled with an overlay) to insert a plugin for managing DNS validation
# with Cloudflare's DNS API.
{ config, pkgs, lib, ... }: { { config, pkgs, lib, ... }: {
options = { options = {
@ -42,12 +53,17 @@
configFile = pkgs.writeText "Caddyfile" (builtins.toJSON { configFile = pkgs.writeText "Caddyfile" (builtins.toJSON {
apps.http.servers.main = { apps.http.servers.main = {
listen = [ ":443" ]; listen = [ ":443" ];
# These routes are pulled from the rest of this repo
routes = config.caddy.routes; routes = config.caddy.routes;
errors.routes = config.caddy.blocks; errors.routes = config.caddy.blocks;
logs = { }; # Uncomment to collect access logs
logs = { }; # Uncommenting collects access logs
}; };
apps.http.servers.metrics = { }; # Enables Prometheus metrics apps.http.servers.metrics = { }; # Enables Prometheus metrics
apps.tls.automation.policies = config.caddy.tlsPolicies; apps.tls.automation.policies = config.caddy.tlsPolicies;
# Setup logging to file
logging.logs.main = { logging.logs.main = {
encoder = { format = "console"; }; encoder = { format = "console"; };
writer = { writer = {
@ -58,13 +74,23 @@
}; };
level = "INFO"; level = "INFO";
}; };
}); });
}; };
# Allows Caddy to serve lower ports (443, 80)
systemd.services.caddy.serviceConfig.AmbientCapabilities =
"CAP_NET_BIND_SERVICE";
# Required for web traffic to reach this machine
networking.firewall.allowedTCPPorts = [ 80 443 ]; networking.firewall.allowedTCPPorts = [ 80 443 ];
# HTTP/3 QUIC uses UDP (not sure if being used)
networking.firewall.allowedUDPPorts = [ 443 ]; networking.firewall.allowedUDPPorts = [ 443 ];
# Caddy exposes Prometheus metrics with the admin API
# https://caddyserver.com/docs/api
prometheus.scrapeTargets = [ "127.0.0.1:2019" ]; prometheus.scrapeTargets = [ "127.0.0.1:2019" ];
}; };

View File

@ -1,3 +1,9 @@
# Calibre-web is an E-Book library and management tool.
# - Exposed to the public via Caddy.
# - Hostname defined with config.hostnames.books
# - File directory backed up to S3 on a cron schedule.
{ config, pkgs, lib, ... }: { config, pkgs, lib, ... }:
let let
@ -26,6 +32,7 @@ in {
}; };
}; };
# Allow web traffic to Caddy
caddy.routes = [{ caddy.routes = [{
match = [{ host = [ config.hostnames.books ]; }]; match = [{ host = [ config.hostnames.books ]; }];
handle = [{ handle = [{
@ -35,6 +42,8 @@ in {
builtins.toString config.services.calibre-web.listen.port builtins.toString config.services.calibre-web.listen.port
}"; }";
}]; }];
# This is required when calibre-web is behind a reverse proxy
# https://github.com/janeczku/calibre-web/issues/19
headers.request.add."X-Script-Name" = [ "/calibre-web" ]; headers.request.add."X-Script-Name" = [ "/calibre-web" ];
}]; }];
}]; }];

View File

@ -1,3 +1,12 @@
# Cloudflare Tunnel is a service for accessing the network even behind a
# firewall, through outbound-only requests. It works by installing an agent on
# our machines that exposes services through Cloudflare Access (Zero Trust),
# similar to something like Tailscale.
# In this case, we're using Cloudflare Tunnel to enable SSH access over a web
# browser even when outside of my network. This is probably not the safest
# choice but I feel comfortable enough with it anyway.
{ config, lib, ... }: { config, lib, ... }:
# First time setup: # First time setup:
@ -40,23 +49,28 @@
tunnels = { tunnels = {
"${config.cloudflareTunnel.id}" = { "${config.cloudflareTunnel.id}" = {
credentialsFile = config.secrets.cloudflared.dest; credentialsFile = config.secrets.cloudflared.dest;
# Catch-all if no match (should never happen anyway)
default = "http_status:404"; default = "http_status:404";
# Match from ingress of any valid server name to SSH access
ingress = { "*.masu.rs" = "ssh://localhost:22"; }; ingress = { "*.masu.rs" = "ssh://localhost:22"; };
}; };
}; };
}; };
# Grant Cloudflare access to SSH into this server
environment.etc = { environment.etc = {
"ssh/ca.pub".text = '' "ssh/ca.pub".text = ''
${config.cloudflareTunnel.ca} ${config.cloudflareTunnel.ca}
''; '';
# Must match the username of the email address in Cloudflare Access # Must match the username portion of the email address in Cloudflare
# Access
"ssh/authorized_principals".text = '' "ssh/authorized_principals".text = ''
${config.user} ${config.user}
''; '';
}; };
# Adjust SSH config to allow access from Cloudflare's certificate
services.openssh.extraConfig = '' services.openssh.extraConfig = ''
PubkeyAuthentication yes PubkeyAuthentication yes
TrustedUserCAKeys /etc/ssh/ca.pub TrustedUserCAKeys /etc/ssh/ca.pub

View File

@ -1,5 +1,13 @@
# This module is necessary for hosts that are serving through Cloudflare. # This module is necessary for hosts that are serving through Cloudflare.
# Cloudflare is a CDN service that is used to serve the domain names and
# caching for my websites and services. Since Cloudflare acts as our proxy, we
# must allow access over the Internet from Cloudflare's IP ranges.
# We also want to validate our HTTPS certificates from Caddy. We'll use Caddy's
# DNS validation plugin to connect to Cloudflare and automatically create
# validation DNS records for our generated certificates.
{ config, pkgs, lib, ... }: { config, pkgs, lib, ... }:
let let
@ -59,10 +67,9 @@ in {
}; };
}]; }];
}]; }];
# Allow Caddy to read Cloudflare API key for DNS validation
systemd.services.caddy.serviceConfig.EnvironmentFile = systemd.services.caddy.serviceConfig.EnvironmentFile =
config.secrets.cloudflareApi.dest; config.secrets.cloudflareApi.dest;
systemd.services.caddy.serviceConfig.AmbientCapabilities =
"CAP_NET_BIND_SERVICE";
# API key must have access to modify Cloudflare DNS records # API key must have access to modify Cloudflare DNS records
secrets.cloudflareApi = { secrets.cloudflareApi = {

View File

@ -1,3 +1,6 @@
# This file imports all the other files in this directory for use as modules in
# my config.
{ ... }: { { ... }: {
imports = [ imports = [

View File

@ -1,3 +1,9 @@
# Gitea Actions is a CI/CD service for the Gitea source code server, meaning it
# allows us to run code operations (such as testing or deploys) when our git
# repositories are updated. Any machine can act as a Gitea Action Runner, so
# the Runners don't necessarily need to be running Gitea. All we need is an API
# key for Gitea to connect to it and register ourselves as a Runner.
{ config, pkgs, lib, ... }: { config, pkgs, lib, ... }:
{ {

View File

@ -11,11 +11,21 @@ in {
actions.ENABLED = true; actions.ENABLED = true;
metrics.ENABLED = true; metrics.ENABLED = true;
repository = { repository = {
# Pushing to a repo that doesn't exist automatically creates one as
# private.
DEFAULT_PUSH_CREATE_PRIVATE = true; DEFAULT_PUSH_CREATE_PRIVATE = true;
# Allow git over HTTP.
DISABLE_HTTP_GIT = false; DISABLE_HTTP_GIT = false;
# Allow requests hitting the specified hostname.
ACCESS_CONTROL_ALLOW_ORIGIN = config.hostnames.git; ACCESS_CONTROL_ALLOW_ORIGIN = config.hostnames.git;
# Automatically create viable users/orgs on push.
ENABLE_PUSH_CREATE_USER = true; ENABLE_PUSH_CREATE_USER = true;
ENABLE_PUSH_CREATE_ORG = true; ENABLE_PUSH_CREATE_ORG = true;
# Default when creating new repos.
DEFAULT_BRANCH = "main"; DEFAULT_BRANCH = "main";
}; };
server = { server = {
@ -25,11 +35,15 @@ in {
SSH_PORT = 22; SSH_PORT = 22;
START_SSH_SERVER = false; # Use sshd instead START_SSH_SERVER = false; # Use sshd instead
DISABLE_SSH = false; DISABLE_SSH = false;
# SSH_LISTEN_HOST = "0.0.0.0";
# SSH_LISTEN_PORT = 122;
}; };
# Don't allow public users to register accounts.
service.DISABLE_REGISTRATION = true; service.DISABLE_REGISTRATION = true;
# Force using HTTPS for all session access.
session.COOKIE_SECURE = true; session.COOKIE_SECURE = true;
# Hide users' emails.
ui.SHOW_USER_EMAIL = false; ui.SHOW_USER_EMAIL = false;
}; };
extraConfig = null; extraConfig = null;
@ -39,6 +53,7 @@ in {
users.users.${config.user}.extraGroups = [ "gitea" ]; users.users.${config.user}.extraGroups = [ "gitea" ];
caddy.routes = [ caddy.routes = [
# Prevent public access to Prometheus metrics.
{ {
match = [{ match = [{
host = [ config.hostnames.git ]; host = [ config.hostnames.git ];
@ -49,6 +64,7 @@ in {
status_code = "403"; status_code = "403";
}]; }];
} }
# Allow access to primary server.
{ {
match = [{ host = [ config.hostnames.git ]; }]; match = [{ host = [ config.hostnames.git ]; }];
handle = [{ handle = [{
@ -63,6 +79,7 @@ in {
} }
]; ];
# Scrape the metrics endpoint for Prometheus.
prometheus.scrapeTargets = [ prometheus.scrapeTargets = [
"127.0.0.1:${ "127.0.0.1:${
builtins.toString config.services.gitea.settings.server.HTTP_PORT builtins.toString config.services.gitea.settings.server.HTTP_PORT

View File

@ -1,3 +1,5 @@
# GPG is an encryption tool. This isn't really in use for me at the moment.
{ config, pkgs, lib, ... }: { { config, pkgs, lib, ... }: {
options.gpg.enable = lib.mkEnableOption "GnuPG encryption."; options.gpg.enable = lib.mkEnableOption "GnuPG encryption.";

View File

@ -7,6 +7,7 @@ in {
config = lib.mkIf config.services.grafana.enable { config = lib.mkIf config.services.grafana.enable {
# Allow Grafana to connect to email service
secrets.mailpass-grafana = { secrets.mailpass-grafana = {
source = ../../../private/mailpass-grafana.age; source = ../../../private/mailpass-grafana.age;
dest = "${config.secretsDirectory}/mailpass-grafana"; dest = "${config.secretsDirectory}/mailpass-grafana";

View File

@ -1,7 +1,10 @@
{ config, lib, pkgs, ... }: # This is a tool for blocking IPs of anyone who attempts to scan all of my
# ports.
# Currently has some issues that don't make this viable. # Currently has some issues that don't make this viable.
{ config, lib, pkgs, ... }:
# Taken from: # Taken from:
# https://dataswamp.org/~solene/2022-09-29-iblock-implemented-in-nixos.html # https://dataswamp.org/~solene/2022-09-29-iblock-implemented-in-nixos.html

View File

@ -1,3 +1,8 @@
# InfluxDB is a timeseries database similar to Prometheus. While
# VictoriaMetrics can also act as an InfluxDB, this version of it allows for
# infinite retention separate from our other metrics, which can be nice for
# recording health information, for example.
{ config, lib, ... }: { { config, lib, ... }: {
config = { config = {

View File

@ -1,3 +1,6 @@
# Jellyfin is a self-hosted video streaming service. This means I can play my
# server's videos from a webpage, mobile app, or TV client.
{ config, pkgs, lib, ... }: { { config, pkgs, lib, ... }: {
config = lib.mkIf config.services.jellyfin.enable { config = lib.mkIf config.services.jellyfin.enable {
@ -6,6 +9,7 @@
users.users.jellyfin = { isSystemUser = true; }; users.users.jellyfin = { isSystemUser = true; };
caddy.routes = [ caddy.routes = [
# Prevent public access to Prometheus metrics.
{ {
match = [{ match = [{
host = [ config.hostnames.stream ]; host = [ config.hostnames.stream ];
@ -16,6 +20,7 @@
status_code = "403"; status_code = "403";
}]; }];
} }
# Allow access to normal route.
{ {
match = [{ host = [ config.hostnames.stream ]; }]; match = [{ host = [ config.hostnames.stream ]; }];
handle = [{ handle = [{

View File

@ -1,3 +1,6 @@
# Keybase is an encrypted communications tool with a synchronized encrypted
# filestore that can be mounted onto a machine's filesystem.
{ config, pkgs, lib, ... }: { { config, pkgs, lib, ... }: {
options.keybase.enable = lib.mkEnableOption "Keybase."; options.keybase.enable = lib.mkEnableOption "Keybase.";

View File

@ -1,3 +1,5 @@
# Mullvad is a VPN service. This isn't currently in use for me at the moment.
{ config, pkgs, lib, ... }: { { config, pkgs, lib, ... }: {
options.mullvad.enable = lib.mkEnableOption "Mullvad VPN."; options.mullvad.enable = lib.mkEnableOption "Mullvad VPN.";

View File

@ -1,3 +1,6 @@
# n8n is an automation integration tool for connecting data from services
# together with triggers.
{ config, lib, ... }: { { config, lib, ... }: {
options = { options = {

View File

@ -1,3 +1,6 @@
# Netdata is an out-of-the-box monitoring tool that exposes many different
# metrics. Not currently in use, in favor of VictoriaMetrics and Grafana.
{ config, lib, ... }: { { config, lib, ... }: {
options.netdata.enable = lib.mkEnableOption "Netdata metrics."; options.netdata.enable = lib.mkEnableOption "Netdata metrics.";

View File

@ -16,12 +16,14 @@
}; };
extraOptions = { extraOptions = {
default_phone_region = "US"; default_phone_region = "US";
# Allow access when hitting either of these hosts or IPs
trusted_domains = [ config.hostnames.content ]; trusted_domains = [ config.hostnames.content ];
trusted_proxies = [ "127.0.0.1" ]; trusted_proxies = [ "127.0.0.1" ];
}; };
extraAppsEnable = true; extraAppsEnable = true;
extraApps = with config.services.nextcloud.package.packages.apps; { extraApps = with config.services.nextcloud.package.packages.apps; {
inherit calendar contacts; inherit calendar contacts;
# These apps are defined and pinned by overlay in flake.
news = pkgs.nextcloudApps.news; news = pkgs.nextcloudApps.news;
external = pkgs.nextcloudApps.external; external = pkgs.nextcloudApps.external;
cookbook = pkgs.nextcloudApps.cookbook; cookbook = pkgs.nextcloudApps.cookbook;

View File

@ -1,3 +1,5 @@
# Paperless-ngx is a document scanning and management solution.
{ config, lib, ... }: { { config, lib, ... }: {
config = lib.mkIf config.services.paperless.enable { config = lib.mkIf config.services.paperless.enable {

View File

@ -1,3 +1,9 @@
# Prometheus is a timeseries database that exposes system and service metrics
# for use in visualizing, monitoring, and alerting (with Grafana).
# Instead of running traditional Prometheus, I generally run VictoriaMetrics as
# a more efficient drop-in replacement.
{ config, pkgs, lib, ... }: { { config, pkgs, lib, ... }: {
options.prometheus = { options.prometheus = {

View File

@ -1,3 +1,5 @@
# Samba is a Windows-compatible file-sharing service.
{ config, lib, ... }: { { config, lib, ... }: {
config = { config = {

View File

@ -1,3 +1,5 @@
# SSHD service for allowing SSH access to my machines.
{ config, pkgs, lib, ... }: { { config, pkgs, lib, ... }: {
options = { options = {

View File

@ -1,3 +1,6 @@
# Transmission is a bittorrent client, which can run in the background for
# automated downloads with a web GUI.
{ config, pkgs, lib, ... }: { { config, pkgs, lib, ... }: {
options = { options = {

View File

@ -1,3 +1,7 @@
# Vaultwarden is an implementation of the Bitwarden password manager backend
# service, which allows for self-hosting the synchronization of a Bitwarden
# password manager client.
{ config, pkgs, lib, ... }: { config, pkgs, lib, ... }:
let vaultwardenPath = "/var/lib/bitwarden_rs"; # Default service directory let vaultwardenPath = "/var/lib/bitwarden_rs"; # Default service directory

View File

@ -1,3 +1,6 @@
# VictoriaMetrics is a more efficient drop-in replacement for Prometheus and
# InfluxDB (timeseries databases built for monitoring system metrics).
{ config, pkgs, lib, ... }: { config, pkgs, lib, ... }:
let let

View File

@ -1,3 +1,6 @@
# Wireguard is a VPN protocol that can be setup to create a mesh network
# between machines on different LANs. This is currently not in use in my setup.
{ config, pkgs, lib, ... }: { { config, pkgs, lib, ... }: {
options.wireguard.enable = lib.mkEnableOption "Wireguard VPN setup."; options.wireguard.enable = lib.mkEnableOption "Wireguard VPN setup.";