mirror of
https://github.com/thegeneralist01/config.git
synced 2026-10-09 21:03:30 +02:00
nixos: migrate server services to thegeneralist
Move hosted services and secrets from the retired central hosts, persist USB storage and ownership, harden RTL8822BU Wi-Fi, repair DNS behavior, and order CoreDNS after Tailscale.
This commit is contained in:
parent
7f5ecd917b
commit
bd887883f7
41 changed files with 183 additions and 511 deletions
BIN
hosts/thegeneralist/acme/acmeEnvironment.age
Normal file
BIN
hosts/thegeneralist/acme/acmeEnvironment.age
Normal file
Binary file not shown.
38
hosts/thegeneralist/acme/default.nix
Normal file
38
hosts/thegeneralist/acme/default.nix
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
{ config, ... }: let
|
||||
domain = "thegeneralist01.com";
|
||||
in {
|
||||
age.secrets.acmeEnvironment.file = ./acmeEnvironment.age;
|
||||
|
||||
security.acme = {
|
||||
defaults = {
|
||||
# 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} = {
|
||||
extraDomainNames = [ "*.${domain}" ];
|
||||
group = "acme";
|
||||
};
|
||||
"git.${domain}" = {
|
||||
group = "acme";
|
||||
};
|
||||
"internal.${domain}" = {
|
||||
group = "acme";
|
||||
};
|
||||
"plex.${domain}" = {
|
||||
group = "acme";
|
||||
};
|
||||
"archive.${domain}" = {
|
||||
group = "acme";
|
||||
};
|
||||
};
|
||||
|
||||
acceptTerms = true;
|
||||
};
|
||||
|
||||
users.groups.acme.members = [ "nginx" ];
|
||||
}
|
||||
34
hosts/thegeneralist/archive/archivebox.nix
Normal file
34
hosts/thegeneralist/archive/archivebox.nix
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
{ pkgs, ... }:
|
||||
{
|
||||
# virtualisation.docker.enable = true;
|
||||
virtualisation.podman = {
|
||||
enable = true;
|
||||
dockerCompat = 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.108.125.63/32";
|
||||
};
|
||||
};
|
||||
|
||||
# pywb = {
|
||||
# image = "docker.io/webrecorder/pywb";
|
||||
# ports = [ "127.0.0.1:8001:8001" ];
|
||||
# volumes = [
|
||||
# "/mnt/usb/services/browsertrix/webrecorder/:/"
|
||||
# "/mnt/usb/services/browsertrix/webrecorder/webarchive:/webarchive"
|
||||
# ];
|
||||
# };
|
||||
};
|
||||
|
||||
environment.systemPackages = [ pkgs.docker ];
|
||||
}
|
||||
43
hosts/thegeneralist/archive/default.nix
Normal file
43
hosts/thegeneralist/archive/default.nix
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
let
|
||||
acmeDomain = "thegeneralist01.com";
|
||||
domain = "archive.${acmeDomain}";
|
||||
|
||||
ssl = {
|
||||
forceSSL = true;
|
||||
quic = true;
|
||||
useACMEHost = acmeDomain;
|
||||
};
|
||||
in
|
||||
{
|
||||
imports = [ ./archivebox.nix ];
|
||||
|
||||
services.nginx.virtualHosts.${domain} = ssl // {
|
||||
listen = [
|
||||
{
|
||||
addr = "100.108.125.63";
|
||||
port = 443;
|
||||
ssl = true;
|
||||
}
|
||||
{
|
||||
addr = "100.108.125.63";
|
||||
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;
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
28
hosts/thegeneralist/cache/default.nix
vendored
Normal file
28
hosts/thegeneralist/cache/default.nix
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
{ pkgs, config, ... }: let
|
||||
domain = "cache.thegeneralist01.com";
|
||||
|
||||
ssl = {
|
||||
quic = true;
|
||||
useACMEHost = "thegeneralist01.com";
|
||||
};
|
||||
in {
|
||||
age.secrets.cacheSigningKey.file = ./key.age;
|
||||
services.nix-serve = {
|
||||
enable = true;
|
||||
package = pkgs.nix-serve-ng;
|
||||
secretKeyFile = config.age.secrets.cacheSigningKey.path;
|
||||
port = 1337;
|
||||
openFirewall = false;
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts.${domain} = ssl // {
|
||||
locations."/".proxyPass = "http://127.0.0.1:1337";
|
||||
locations."= /".return = "301 @404";
|
||||
locations."@404".return = "404 https://thegeneralist01.com/404";
|
||||
|
||||
extraConfig = /* nginx */ ''
|
||||
proxy_intercept_errors on;
|
||||
error_page 404 = @404;
|
||||
'';
|
||||
};
|
||||
}
|
||||
7
hosts/thegeneralist/cache/key.age
vendored
Normal file
7
hosts/thegeneralist/cache/key.age
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 pp9qdQ hxgD5olkewZpdkhEmVaGYypGzM403Xa7INBRzt78+kU
|
||||
LTj/042NIvyLcDA3VpWO6M+pdl2fhzjyXzA3jWP+III
|
||||
--- E7wuA8Hb4tpfvqQtPxexcGGK9ng/NVhI16XcErKVAFE
|
||||
-þŒüá8'ß|Ú<>dù²ùÊ#j9•(æ
|
||||
MAF+‹[¼û’eNk_Ñ2\ËÆº#d"øÅð§‹Òµw<PPU- »M¶G.Ôõ«G/?™¾(qØ#{_C}IìJÚT…Ï»
|
||||
NHQãm¨ †^H¶´É¶<C389>ž òY±{aÈÙgfŽs“S›šø.À
|
||||
BIN
hosts/thegeneralist/cert.pem.age
Normal file
BIN
hosts/thegeneralist/cert.pem.age
Normal file
Binary file not shown.
|
|
@ -6,11 +6,37 @@
|
|||
config,
|
||||
pkgs,
|
||||
inputs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
{
|
||||
imports = [ ./hardware-configuration.nix ];
|
||||
imports = [
|
||||
./hardware-configuration.nix
|
||||
./site.nix
|
||||
./cache
|
||||
./archive
|
||||
./forgejo
|
||||
];
|
||||
|
||||
age.secrets.readlaterBotToken = {
|
||||
file = ./readlater-bot-token.age;
|
||||
owner = "thegeneralist";
|
||||
group = "users";
|
||||
mode = "0400";
|
||||
};
|
||||
age.secrets.readlaterBotSyncToken = {
|
||||
file = ./readlater-bot-sync-token.age;
|
||||
owner = "thegeneralist";
|
||||
group = "users";
|
||||
mode = "0400";
|
||||
};
|
||||
age.secrets.readlaterBotUserId = {
|
||||
file = ./readlater-bot-user-id.age;
|
||||
owner = "thegeneralist";
|
||||
group = "users";
|
||||
mode = "0400";
|
||||
};
|
||||
|
||||
users.users.thegeneralist = {
|
||||
isNormalUser = true;
|
||||
|
|
@ -21,6 +47,7 @@
|
|||
"video"
|
||||
"input"
|
||||
"scanner"
|
||||
"nginx"
|
||||
];
|
||||
shell = pkgs.zsh;
|
||||
home = "/home/thegeneralist";
|
||||
|
|
@ -49,6 +76,43 @@
|
|||
}
|
||||
];
|
||||
|
||||
services.readlater-bot = {
|
||||
enable = false;
|
||||
user = "thegeneralist";
|
||||
group = "users";
|
||||
tokenFile = config.age.secrets.readlaterBotToken.path;
|
||||
settings = {
|
||||
media_dir = "/home/thegeneralist/obsidian/09 Misc/Assets/images_misc";
|
||||
resources_path = "/home/thegeneralist/obsidian/02 Knowledge/03 Resources";
|
||||
read_later_path = "/home/thegeneralist/obsidian/10 Read Later.md";
|
||||
finished_path = "/home/thegeneralist/obsidian/20 Finished Reading.md";
|
||||
data_dir = "/var/lib/readlater-bot";
|
||||
retry_interval_seconds = 30;
|
||||
sync = {
|
||||
repo_path = "/home/thegeneralist/obsidian";
|
||||
token_file = config.age.secrets.readlaterBotSyncToken.path;
|
||||
};
|
||||
sync_x = {
|
||||
source_project_path = "/home/thegeneralist/bookkeeper/vendor/extract-x-bookmarks";
|
||||
python_bin = "/home/thegeneralist/bookkeeper/vendor/extract-x-bookmarks/.venv/bin/python3";
|
||||
work_dir = "/home/thegeneralist/bookkeeper/.sync-x-work";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.readlater-bot.preStart = lib.mkAfter ''
|
||||
if [ -f /run/readlater-bot/config.toml ]; then
|
||||
tmp="/run/readlater-bot/config.toml.tmp"
|
||||
{
|
||||
IFS= read -r first_line || true
|
||||
printf '%s\n' "$first_line"
|
||||
printf 'user_id = %s\n' "$(cat ${config.age.secrets.readlaterBotUserId.path})"
|
||||
cat
|
||||
} < /run/readlater-bot/config.toml > "$tmp"
|
||||
mv "$tmp" /run/readlater-bot/config.toml
|
||||
fi
|
||||
'';
|
||||
|
||||
# Some programs
|
||||
services.libinput.enable = true;
|
||||
programs.firefox.enable = true;
|
||||
|
|
|
|||
BIN
hosts/thegeneralist/credentials.age
Normal file
BIN
hosts/thegeneralist/credentials.age
Normal file
Binary file not shown.
53
hosts/thegeneralist/dns.nix
Normal file
53
hosts/thegeneralist/dns.nix
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
{ pkgs, lib, ... }:
|
||||
let
|
||||
subdomains = [ "internal" "archive" "plex" ];
|
||||
|
||||
mainZoneFile = pkgs.writeText "thegeneralist01.zone" ''
|
||||
$ORIGIN thegeneralist01.com.
|
||||
@ IN SOA ns.thegeneralist01.com. thegeneralist01.proton.me. (
|
||||
2025081501 ; serial (yyyymmddXX)
|
||||
3600 ; refresh
|
||||
600 ; retry
|
||||
86400 ; expire
|
||||
3600 ; minimum
|
||||
)
|
||||
IN NS ns.thegeneralist01.com.
|
||||
ns IN A 100.108.125.63
|
||||
${lib.concatStringsSep "\n" (lib.map (sub: "${sub} IN A 100.108.125.63") subdomains)}
|
||||
'';
|
||||
|
||||
forwarderBlock = ''
|
||||
.:53 {
|
||||
bind 100.108.125.63
|
||||
forward . 100.100.100.100 45.90.28.181 45.90.30.181
|
||||
cache
|
||||
log
|
||||
errors
|
||||
}
|
||||
'';
|
||||
in
|
||||
{
|
||||
services.coredns = {
|
||||
enable = true;
|
||||
config = ''
|
||||
thegeneralist01.com:53 {
|
||||
bind 100.108.125.63
|
||||
file ${mainZoneFile}
|
||||
log
|
||||
errors
|
||||
}
|
||||
|
||||
${forwarderBlock}
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.services.coredns = {
|
||||
wants = [ "network-online.target" "tailscaled.service" ];
|
||||
after = [ "network-online.target" "tailscaled.service" ];
|
||||
serviceConfig = {
|
||||
Restart = "on-failure";
|
||||
RestartSec = "2s";
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
174
hosts/thegeneralist/forgejo/default.nix
Normal file
174
hosts/thegeneralist/forgejo/default.nix
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
forgejoStateDir = "/mnt/usb/services/forgejo/stateDir";
|
||||
domain = "git.thegeneralist01.com";
|
||||
in
|
||||
{
|
||||
imports = [ ../../../modules/postgresql.nix ];
|
||||
|
||||
age.secrets.forgejoRunnerToken.file = ./forgejo-runner-token.age;
|
||||
|
||||
services.forgejo = {
|
||||
enable = true;
|
||||
stateDir = forgejoStateDir;
|
||||
|
||||
lfs.enable = true;
|
||||
|
||||
settings =
|
||||
let
|
||||
title = "thegeneralist01's forgejo";
|
||||
desc = "the attic of thegeneralist01's random repositories";
|
||||
in
|
||||
{
|
||||
default.APP_NAME = title;
|
||||
"ui.meta" = {
|
||||
AUTHOR = title;
|
||||
DESCRIPTION = desc;
|
||||
};
|
||||
|
||||
attachment.ALLOWED_TYPES = "*/*";
|
||||
actions = {
|
||||
ENABLED = true;
|
||||
};
|
||||
cache.ENABLED = true;
|
||||
|
||||
"cron.archive_cleanup" =
|
||||
let
|
||||
interval = "4h";
|
||||
in
|
||||
{
|
||||
SCHEDULE = "@every ${interval}";
|
||||
OLDER_THAN = interval;
|
||||
};
|
||||
|
||||
packages.ENABLED = true;
|
||||
mailer = {
|
||||
ENABLED = false;
|
||||
|
||||
# PROTOCOL = "smtps";
|
||||
# SMTP_ADDR = self.disk.mailserver.fqdn;
|
||||
# USER = "git@${domain}";
|
||||
};
|
||||
|
||||
other = {
|
||||
SHOW_FOOTER_TEMPLATE_LOAD_TIME = false;
|
||||
SHOW_FOOTER_VERSION = false;
|
||||
};
|
||||
|
||||
repository = {
|
||||
DEFAULT_BRANCH = "master";
|
||||
DEFAULT_MERGE_STYLE = "rebase-merge";
|
||||
DEFAULT_REPO_UNITS = "repo.code, repo.issues, repo.pulls";
|
||||
|
||||
DEFAULT_PUSH_CREATE_PRIVATE = false;
|
||||
ENABLE_PUSH_CREATE_ORG = true;
|
||||
ENABLE_PUSH_CREATE_USER = true;
|
||||
|
||||
DISABLE_STARS = true;
|
||||
};
|
||||
|
||||
"repository.upload" = {
|
||||
FILE_MAX_SIZE = 100;
|
||||
MAX_FILES = 10;
|
||||
};
|
||||
|
||||
server = {
|
||||
ROOT_URL = "https://${domain}/";
|
||||
DOMAIN = domain;
|
||||
LANDING_PAGE = "/explore";
|
||||
|
||||
HTTP_ADDR = "127.0.0.1";
|
||||
HTTP_PORT = 3000;
|
||||
|
||||
SSH_LISTEN_HOST = "0.0.0.0";
|
||||
SSH_PORT = 2222;
|
||||
SSH_LISTEN_PORT = 2222;
|
||||
};
|
||||
|
||||
service.DISABLE_REGISTRATION = true;
|
||||
|
||||
session = {
|
||||
COOKIE_SECURE = true;
|
||||
SAME_SITE = "strict";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.forgejo = {
|
||||
requires = [ "forgejo-data-ownership.service" ];
|
||||
after = [ "forgejo-data-ownership.service" ];
|
||||
};
|
||||
|
||||
systemd.services.forgejo-data-ownership = {
|
||||
description = "Prepare persisted Forgejo state";
|
||||
unitConfig.RequiresMountsFor = [ forgejoStateDir ];
|
||||
before = [ "forgejo.service" ];
|
||||
serviceConfig.Type = "oneshot";
|
||||
script = ''
|
||||
marker=${forgejoStateDir}/.nixos-ownership-v1
|
||||
if [ ! -e "$marker" ]; then
|
||||
${pkgs.coreutils}/bin/chown -R forgejo:forgejo ${forgejoStateDir}
|
||||
${pkgs.coreutils}/bin/touch "$marker"
|
||||
${pkgs.coreutils}/bin/chown forgejo:forgejo "$marker"
|
||||
fi
|
||||
'';
|
||||
};
|
||||
|
||||
services.gitea-actions-runner = {
|
||||
package = pkgs.forgejo-runner;
|
||||
instances.thegeneralist = {
|
||||
enable = true;
|
||||
name = "thegeneralist";
|
||||
url = "https://${domain}";
|
||||
tokenFile = config.age.secrets.forgejoRunnerToken.path;
|
||||
labels = [
|
||||
"native:host"
|
||||
# "node-22:docker://node:22-bookworm"
|
||||
# "nixos-latest:docker://nixos/nix"
|
||||
];
|
||||
|
||||
# Host-executed jobs need nix + ssh in PATH.
|
||||
hostPackages = with pkgs; [
|
||||
bash
|
||||
coreutils
|
||||
curl
|
||||
gawk
|
||||
gitMinimal
|
||||
gnused
|
||||
nodejs
|
||||
nix
|
||||
openssh
|
||||
wget
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall.trustedInterfaces = [ "br-+" ];
|
||||
|
||||
|
||||
# Avoid /var/lib/private so the runner can write its state.
|
||||
systemd.services.gitea-runner-thegeneralist.serviceConfig = {
|
||||
DynamicUser = lib.mkForce false;
|
||||
StateDirectory = lib.mkForce "gitea-runner";
|
||||
StateDirectoryMode = "0755";
|
||||
# Ensure newly created files are group-writable for the shared repo.
|
||||
UMask = "0002";
|
||||
};
|
||||
|
||||
users.groups.gitea-runner = { };
|
||||
users.users.gitea-runner = {
|
||||
isSystemUser = true;
|
||||
group = "gitea-runner";
|
||||
extraGroups = [ "users" ];
|
||||
home = "/var/lib/gitea-runner/thegeneralist";
|
||||
createHome = true;
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d /var/lib/gitea-runner 0755 gitea-runner gitea-runner -"
|
||||
"d /var/lib/gitea-runner/thegeneralist 0755 gitea-runner gitea-runner -"
|
||||
];
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ 2222 ];
|
||||
}
|
||||
6
hosts/thegeneralist/forgejo/forgejo-runner-token.age
Normal file
6
hosts/thegeneralist/forgejo/forgejo-runner-token.age
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 pp9qdQ 3zI5p1EPKcJdRWK0ZikK7MEwLON9oX2qRy0Ll8+7rXE
|
||||
+66HhKgUa3AsYO4gHQmlypR7CgkdaQI7goZCPTHGxEE
|
||||
--- R+2xHNQawIBenqYp5t4s7XGDeDLt9cFZXprJSNHe8dE
|
||||
74›îA<0X”oexõúVüåÂn²#AÁeàDSã}þb¡§I ¤´Ðh“ÔÇD!`¥QB¿œˆ[û:ˆÛÙúf§$æñÁ™¦—ÏC?2Û“›Möþ
Ô‰ƒNC÷ŸU2¡ÉNuèý
|
||||
¸é@&Ç s‚©«÷ؽ‹Ìs…ñ<E280A6>DÉãsdÚÐÞÓ–D¸Xˆ1nØJ
|
||||
|
|
@ -13,6 +13,16 @@
|
|||
hardware.enableAllFirmware = true;
|
||||
boot.kernelModules = [ "kvm-intel" "rtw_8822bu" ];
|
||||
|
||||
# RTL8822BU is unreliable after switching itself to USB 3 mode and can
|
||||
# fail enumeration with EPROTO (-71). Keep it in USB 2 mode and disable
|
||||
# the power-saving states that can wedge rtw88 USB adapters.
|
||||
boot.extraModprobeConfig = ''
|
||||
options rtw88_usb switch_usb_mode=N
|
||||
options rtw88_core disable_lps_deep=1
|
||||
'';
|
||||
boot.kernelParams = [ "usbcore.autosuspend=-1" ];
|
||||
networking.networkmanager.wifi.powersave = false;
|
||||
|
||||
fileSystems."/" =
|
||||
{
|
||||
device = "/dev/disk/by-label/NIXROOT";
|
||||
|
|
@ -26,6 +36,15 @@
|
|||
options = [ "fmask=0022" "dmask=0022" ];
|
||||
};
|
||||
|
||||
fileSystems."/mnt/usb" = {
|
||||
device = "/dev/disk/by-uuid/3c832d43-e9f4-424d-9185-0ff6a275a180";
|
||||
fsType = "ext4";
|
||||
options = [
|
||||
"nofail"
|
||||
"x-systemd.automount"
|
||||
];
|
||||
};
|
||||
|
||||
swapDevices = [{
|
||||
device = "/dev/disk/by-label/swap";
|
||||
}];
|
||||
|
|
|
|||
80
hosts/thegeneralist/jellyfin/default.nix
Normal file
80
hosts/thegeneralist/jellyfin/default.nix
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
{ 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/services/jellyfin/cache";
|
||||
dataDir = "/mnt/usb/services/jellyfin/data/data";
|
||||
configDir = "/mnt/usb/services/jellyfin/data/config";
|
||||
logDir = "/mnt/usb/services/jellyfin/data/log";
|
||||
};
|
||||
|
||||
systemd.services.jellyfin = {
|
||||
requires = [ "jellyfin-data-ownership.service" ];
|
||||
after = [ "jellyfin-data-ownership.service" ];
|
||||
};
|
||||
|
||||
systemd.services.jellyfin-data-ownership = {
|
||||
description = "Prepare persisted Jellyfin state";
|
||||
unitConfig.RequiresMountsFor = [ "/mnt/usb/services/jellyfin" ];
|
||||
before = [ "jellyfin.service" ];
|
||||
serviceConfig.Type = "oneshot";
|
||||
script = ''
|
||||
marker=/mnt/usb/services/jellyfin/.nixos-ownership-v1
|
||||
if [ ! -e "$marker" ]; then
|
||||
${pkgs.coreutils}/bin/chown -R jellyfin:jellyfin /mnt/usb/services/jellyfin
|
||||
${pkgs.coreutils}/bin/touch "$marker"
|
||||
${pkgs.coreutils}/bin/chown jellyfin:jellyfin "$marker"
|
||||
fi
|
||||
'';
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts.${domain} = ssl // {
|
||||
listen = [
|
||||
{
|
||||
addr = "100.108.125.63";
|
||||
port = 443;
|
||||
ssl = true;
|
||||
}
|
||||
{
|
||||
addr = "100.108.125.63";
|
||||
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;
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
98
hosts/thegeneralist/plex/default.nix
Normal file
98
hosts/thegeneralist/plex/default.nix
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
{ pkgs, ... }:
|
||||
let
|
||||
acmeDomain = "thegeneralist01.com";
|
||||
domain = "plex.${acmeDomain}";
|
||||
|
||||
ssl = {
|
||||
forceSSL = true;
|
||||
quic = true;
|
||||
useACMEHost = domain;
|
||||
};
|
||||
|
||||
|
||||
config = ssl // {
|
||||
listen = [
|
||||
{
|
||||
addr = "100.108.125.63";
|
||||
port = 443;
|
||||
ssl = true;
|
||||
}
|
||||
{
|
||||
addr = "100.108.125.63";
|
||||
port = 80;
|
||||
}
|
||||
];
|
||||
|
||||
locations."/" = {
|
||||
proxyPass = "http://127.0.0.1:32400";
|
||||
proxyWebsockets = true;
|
||||
recommendedProxySettings = true;
|
||||
# https://arne.me/blog/plex-on-nixos
|
||||
extraConfig = ''
|
||||
# Some players don't reopen a socket and playback stops totally instead of resuming after an extended pause
|
||||
send_timeout 100m;
|
||||
# Plex headers
|
||||
proxy_set_header X-Plex-Client-Identifier $http_x_plex_client_identifier;
|
||||
proxy_set_header X-Plex-Device $http_x_plex_device;
|
||||
proxy_set_header X-Plex-Device-Name $http_x_plex_device_name;
|
||||
proxy_set_header X-Plex-Platform $http_x_plex_platform;
|
||||
proxy_set_header X-Plex-Platform-Version $http_x_plex_platform_version;
|
||||
proxy_set_header X-Plex-Product $http_x_plex_product;
|
||||
proxy_set_header X-Plex-Token $http_x_plex_token;
|
||||
proxy_set_header X-Plex-Version $http_x_plex_version;
|
||||
proxy_set_header X-Plex-Nocache $http_x_plex_nocache;
|
||||
proxy_set_header X-Plex-Provides $http_x_plex_provides;
|
||||
proxy_set_header X-Plex-Device-Vendor $http_x_plex_device_vendor;
|
||||
proxy_set_header X-Plex-Model $http_x_plex_model;
|
||||
# Buffering off send to the client as soon as the data is received from Plex.
|
||||
proxy_redirect off;
|
||||
proxy_buffering off;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header Host $host;
|
||||
'';
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
services.plex = {
|
||||
enable = true;
|
||||
package = pkgs.plex;
|
||||
dataDir = "/var/lib/plex";
|
||||
# openFirewall = true;
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d /var/lib/plex/Library/Application\\040Support/Plex\\ Media\\ Server 0755 plex plex -"
|
||||
"f /var/lib/plex/Library/Application\\040Support/Plex\\ Media\\ Server/Preferences.xml 0644 plex plex -"
|
||||
];
|
||||
|
||||
systemd.services.plex-fix-perms = {
|
||||
description = "Fix Plex library permissions";
|
||||
wants = [ "plex.service" ]; # Plex depends on this
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
ExecStart = ''
|
||||
mkdir -p "/var/lib/plex/Library/Application Support/Plex Media Server"
|
||||
chown -R plex:plex "/var/lib/plex/Library/Application Support/Plex Media Server"
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall.interfaces."tailscale0" = {
|
||||
allowedTCPPorts = [ 3005 8324 32469 80 443 ];
|
||||
allowedUDPPorts = [ 1900 5353 32410 32412 32413 32414 ];
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts = {
|
||||
${domain} = config;
|
||||
"100.108.125.63" = config;
|
||||
};
|
||||
|
||||
systemd.services.plex = {
|
||||
wants = [ "network-online.target" "tailscaled.service" ];
|
||||
after = [ "network-online.target" "tailscaled.service" ];
|
||||
};
|
||||
}
|
||||
5
hosts/thegeneralist/readlater-bot-sync-token.age
Normal file
5
hosts/thegeneralist/readlater-bot-sync-token.age
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 pp9qdQ twxKRYACgz/8cYRrOCxMoVg9kFXaYxWVnDC1q7g4m3M
|
||||
HICOhz/phNPvmLrO/ILxoMb5Bbs7LAJ3wuPAq1PJXiQ
|
||||
--- 0yPpaiiJXMaUBa+kBX/UOTMICRjKXMgjRk2E+WKgj+M
|
||||
¡ï6«„£YŸ'Þ\±E<C2B1>T‡cÊP;´Œˆ?œ‘j‚&+íFPÜ<50>*J‡m¦<Ï–~ÉúÐ
Ó˜AI*¢„lÜØŠ×X'˃
|
||||
5
hosts/thegeneralist/readlater-bot-token.age
Normal file
5
hosts/thegeneralist/readlater-bot-token.age
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 pp9qdQ B8+s7rbKTCk2vfRVUyc8yV2HhkiUjv9petRiBRg9kgE
|
||||
9po69JEGIQGXUIyjJj3BOMZGc5qDSbvug1HsO/EgDTE
|
||||
--- n+cCCXuJP4oboSm74DRK9oh/OyHuPSdnX1+lH5xgn0E
|
||||
IŽ´‚ó¼„fëøÎ,(¦Ù†¨äÿ¶S°–d鎕Á^¶QhþˆF{_š<>Ü„§<E2809E>4Õ€Ô
Z™(£Ô¥ŽümubÝÏø€
|
||||
5
hosts/thegeneralist/readlater-bot-user-id.age
Normal file
5
hosts/thegeneralist/readlater-bot-user-id.age
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 pp9qdQ JjYS0OmsdzkazhynwiYUWf6svuUu0ivXi7VrFdccez0
|
||||
0xelpQamzEYTN/TqbJ3kI1OhfZdBl2DhhgKv29qg8J4
|
||||
--- V0a84QEOAyVidy+5KoxJOwsj+XrmlMbg4+oLbHVK0FA
|
||||
D»ž'@0ö*aOÙŽHܯŒm‹tú…ï,¢±Ð«<C390>˜<EFBFBD>€õb£ÁI¥¼
|
||||
52
hosts/thegeneralist/site.nix
Normal file
52
hosts/thegeneralist/site.nix
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
{ config, pkgs, ... }:
|
||||
{
|
||||
imports = [
|
||||
./acme
|
||||
./dns.nix
|
||||
./jellyfin
|
||||
./plex
|
||||
];
|
||||
|
||||
# Nginx
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
enableQuicBPF = true;
|
||||
|
||||
experimentalZstdSettings = true;
|
||||
recommendedUwsgiSettings = true;
|
||||
recommendedTlsSettings = true;
|
||||
recommendedProxySettings = true;
|
||||
recommendedOptimisation = true;
|
||||
recommendedGzipSettings = true;
|
||||
recommendedBrotliSettings = true;
|
||||
|
||||
statusPage = true;
|
||||
validateConfigFile = true;
|
||||
|
||||
# Domain-specific virtual hosts live in the service modules below.
|
||||
};
|
||||
|
||||
# Cloudflare
|
||||
environment.systemPackages = [ pkgs.cloudflared ];
|
||||
|
||||
age.secrets.cftcert.file = ./cert.pem.age;
|
||||
age.secrets.cftcredentials.file = ./credentials.age;
|
||||
|
||||
services.cloudflared = {
|
||||
enable = true;
|
||||
certificateFile = config.age.secrets.cftcert.path;
|
||||
|
||||
tunnels = {
|
||||
"site" = {
|
||||
ingress = {
|
||||
"cache.thegeneralist01.com" = "http://localhost:80";
|
||||
"git.thegeneralist01.com" = "http://localhost:3000";
|
||||
};
|
||||
default = "http_status:404";
|
||||
|
||||
credentialsFile = config.age.secrets.cftcredentials.path;
|
||||
certificateFile = config.age.secrets.cftcert.path;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue