services: add jellyfin, archivebox, custom dns

- `internal.thegeneralist01.com` and `archive.thegeneralist01.com` are
  not public. I have Split DNS enabled on them (in Tailscale), with the
  IP of the DNS server set to a private Tailscale IP of my home server;
- CoreDNS (also on my home server) is used to resolve the two private
  domains' IPs to the home server itself;
- nginx only listens to its machine's (home server's) Tailscale IP;
- Therefore, all of it is hermetic!
This commit is contained in:
TheGeneralist 2025-08-03 14:48:21 +02:00
parent 8724801def
commit 572647d7c4
Signed by: thegeneralist01
SSH key fingerprint: SHA256:pp9qddbCNmVNoSjevdvQvM5z0DHN7LTa8qBMbcMq/R4
9 changed files with 194 additions and 11 deletions

View file

@ -5,17 +5,25 @@ in {
security.acme = {
defaults = {
# Options: https://go-acme.github.io/lego/dns/
# Options: https://go-acme.github.io/lego/dns/acme
environmentFile = config.age.secrets.acmeEnvironment.path;
email = "thegeneralist01@proton.me";
dnsResolver = "1.1.1.1";
dnsProvider = "cloudflare";
};
certs.${domain} = {
certs = {
${domain} = {
extraDomainNames = [ "*.${domain}" ];
group = "acme";
};
"internal.${domain}" = {
group = "acme";
};
"archive.${domain}" = {
group = "acme";
};
};
acceptTerms = true;
};

View file

@ -0,0 +1,41 @@
let
acmeDomain = "thegeneralist01.com";
domain = "archive.${acmeDomain}";
ssl = {
forceSSL = true;
quic = true;
useACMEHost = domain;
};
in
{
services.nginx.virtualHosts.${domain} = ssl // {
listen = [
{
addr = "100.86.129.23";
port = 443;
ssl = true;
}
{
addr = "100.86.129.23";
port = 80;
}
];
locations."/" = {
proxyPass = "http://127.0.0.1:8000";
recommendedProxySettings = true;
extraConfig = ''
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# tell nginx not to buffer the response. send it as it comes.
proxy_buffering off;
# give jellyfin plenty of time to transcode
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
'';
};
};
}

View file

@ -5,14 +5,14 @@
{ config, pkgs, inputs, ... }:
{
imports = [ ./hardware-configuration.nix ./site.nix ./cache ];
imports = [ ./hardware-configuration.nix ./site.nix ./cache ./garage.nix ./archive ];
age.secrets.password.file = ./password.age;
users.users = {
thegeneralist = {
isNormalUser = true;
description = "thegeneralist";
extraGroups = [ "wheel" "audio" "video" "input" "scanner" ];
extraGroups = [ "wheel" "audio" "video" "input" "scanner" "docker" ];
shell = pkgs.zsh;
home = "/home/thegeneralist";
hashedPasswordFile = config.age.secrets.password.path;

View file

@ -0,0 +1,58 @@
{ pkgs, ... }:
let
internalZoneFile = pkgs.writeText "internal.zone" ''
$ORIGIN internal.thegeneralist01.com.
@ IN SOA ns.internal.thegeneralist01.com. thegeneralist01.proton.me. (
2025071801 ; serial (yyyymmddXX)
3600 ; refresh
600 ; retry
86400 ; expire
3600 ; minimum
)
IN NS ns.internal.thegeneralist01.com.
ns IN A 100.86.129.23
@ IN A 100.86.129.23
'';
archiveZoneFile = pkgs.writeText "archive.zone" ''
$ORIGIN archive.thegeneralist01.com.
@ IN SOA ns.archive.thegeneralist01.com. thegeneralist01.proton.me. (
2025073101 ; serial (yyyymmddXX)
3600 ; refresh
600 ; retry
86400 ; expire
3600 ; minimum
)
IN NS ns.archive.thegeneralist01.com.
ns IN A 100.86.129.23
@ IN A 100.86.129.23
'';
in
{
services.coredns = {
enable = true;
config = ''
internal.thegeneralist01.com:53 {
file ${internalZoneFile}
log
errors
}
archive.thegeneralist01.com:53 {
file ${archiveZoneFile}
log
errors
}
.:53 {
forward . 100.100.100.100 45.90.28.181 45.90.30.181
cache
log
errors
}
'';
};
networking.firewall.allowedUDPPorts = [ 53 ];
networking.firewall.allowedTCPPorts = [ 53 ];
}

View file

@ -0,0 +1,18 @@
{ pkgs, ... }: {
virtualisation.docker.enable = true;
virtualisation.oci-containers.containers.archivebox = {
image = "ghcr.io/archivebox/archivebox:main";
ports = [ "127.0.0.1:8000:8000" ];
volumes = [
"/mnt/usb/services/archivebox/data:/data"
];
environment = {
ALLOWLIST_HOSTS = "localhost";
CSRF_TRUSTED_ORIGINS = "https://archive.thegeneralist01.com,127.0.0.1:8000";
REVERSE_PROXY_USER_HEADER = "X-Remote-User";
REVERSE_PROXY_WHITELIST = "127.0.0.1/32,100.86.129.23/32";
};
};
environment.systemPackages = [ pkgs.docker ];
}

View file

@ -0,0 +1,60 @@
{ pkgs, ... }:
let
acmeDomain = "thegeneralist01.com";
domain = "internal.${acmeDomain}";
ssl = {
forceSSL = true;
quic = true;
useACMEHost = domain;
};
in
{
environment.systemPackages = with pkgs; [
jellyfin
jellyfin-web
jellyfin-ffmpeg
];
services.jellyfin = {
enable = true;
package = pkgs.jellyfin;
group = "jellyfin";
user = "jellyfin";
cacheDir = "/mnt/usb/jellyfin/cache";
dataDir = "/mnt/usb/jellyfin/data";
configDir = "/mnt/usb/jellyfin/data/config";
logDir = "/mnt/usb/jellyfin/data/log";
};
services.nginx.virtualHosts.${domain} = ssl // {
listen = [
{
addr = "100.86.129.23";
port = 443;
ssl = true;
}
{
addr = "100.86.129.23";
port = 80;
}
];
locations."/" = {
proxyPass = "http://127.0.0.1:8096";
recommendedProxySettings = true;
extraConfig = ''
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# tell nginx not to buffer the response. send it as it comes.
proxy_buffering off;
# give jellyfin plenty of time to transcode
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
'';
};
};
}

View file

@ -6,7 +6,7 @@
useACMEHost = domain;
};
in {
imports = [ ./acme ];
imports = [ ./acme ./dns.nix ./jellyfin ];
# Nginx
services.nginx = {

View file

@ -46,7 +46,6 @@ autocmd('LspAttach', {
vim.keymap.set("n", "<leader>vca", function() vim.lsp.buf.code_action() end, opts("View code actions"))
vim.keymap.set("i", "<C-]>", function() vim.lsp.buf.code_action() end, opts("View code actions"))
vim.keymap.set("n", "<leader>va", function()
-- TODO: this
local params = vim.lsp.util.make_range_params()
params.context = { diagnostics = vim.lsp.diagnostic.get_line_diagnostics() }
local result, err = vim.lsp.buf_request_sync(0, "textDocument/codeAction", params, 1000)

View file

@ -1,8 +1,7 @@
{ config, lib, ... }: let
inherit (lib) concatStringsSep;
inherit (lib) mkIf concatStringsSep;
in {
# TODO: add fallback & check other options
services.resolved = {
services.resolved = mkIf (!config.isServer) {
enable = true;
extraConfig = config.dnsServers