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
Binary file not shown.
|
|
@ -1,38 +0,0 @@
|
|||
{ 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" ];
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
{ 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.86.129.23/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 ];
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
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.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;
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
28
hosts/thegeneralist-central/cache/default.nix
vendored
28
hosts/thegeneralist-central/cache/default.nix
vendored
|
|
@ -1,28 +0,0 @@
|
|||
{ 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-central/cache/key.age
vendored
7
hosts/thegeneralist-central/cache/key.age
vendored
|
|
@ -1,7 +0,0 @@
|
|||
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›šø.À
|
||||
Binary file not shown.
|
|
@ -1,6 +0,0 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 pp9qdQ OrqCuVIzHaavNZxpOXYlIcnrHJe5GOjtcIhmaw+8wHI
|
||||
TQCYrhgm4O52QPodgSmFMvyw0Ln7n/+vFlGnONctPKk
|
||||
--- jOnFOfG4YRnpvtmmoEVfbh3mAXtfcJiTjzja46xTKMk
|
||||
Ï÷r_z'Dx<44>’y2Ô—ÿƒG½8¹hë}=è‡EàK«[wÀ߯ìöaðÿBÞ
|
||||
÷þH ħÿHîÆbE–ÿì9{´YS‚ÑΖJÞL>²ö¨êPÈÒ“m£ÿDšn¤BQ
Qšš2êÕ¸·çW&uÒ‡X…¼òf»FUoj6Q3e4¡X¸*‚Ý*ó*xÚÓÖ½©ç Cî±ÏýpÇ’}”ÛVµ9~
=û`ô ½¦<C2BD>’AÓI<.÷’GEÀ¨2L1BM‡x›ÿW…½IlŸ–†Ü}2&±âïÖ
|
||||
|
|
@ -1,153 +0,0 @@
|
|||
# Edit this configuration file to define what should be installed on
|
||||
# your system. Help is available in the configuration.nix(5) man page, on
|
||||
# https://search.nixos.org/options and in the NixOS manual (`nixos-help`).
|
||||
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
inputs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
{
|
||||
imports = [
|
||||
./hardware-configuration.nix
|
||||
./site.nix
|
||||
./cache
|
||||
./archive
|
||||
./forgejo
|
||||
];
|
||||
|
||||
age.secrets.password.file = ./password.age;
|
||||
age.secrets.readlaterBotToken.file = ./readlater-bot-token.age;
|
||||
age.secrets.readlaterBotSyncToken.file = ./readlater-bot-sync-token.age;
|
||||
age.secrets.readlaterBotUserId.file = ./readlater-bot-user-id.age;
|
||||
age.secrets.readlaterBotToken.owner = "thegeneralist";
|
||||
age.secrets.readlaterBotToken.group = "users";
|
||||
age.secrets.readlaterBotToken.mode = "0400";
|
||||
age.secrets.readlaterBotSyncToken.owner = "thegeneralist";
|
||||
age.secrets.readlaterBotSyncToken.group = "users";
|
||||
age.secrets.readlaterBotSyncToken.mode = "0400";
|
||||
age.secrets.readlaterBotUserId.owner = "thegeneralist";
|
||||
age.secrets.readlaterBotUserId.group = "users";
|
||||
age.secrets.readlaterBotUserId.mode = "0400";
|
||||
|
||||
users.users = {
|
||||
thegeneralist = {
|
||||
isNormalUser = true;
|
||||
description = "thegeneralist";
|
||||
extraGroups = [
|
||||
"wheel"
|
||||
"audio"
|
||||
"video"
|
||||
"input"
|
||||
"scanner"
|
||||
"docker"
|
||||
"nginx"
|
||||
];
|
||||
shell = pkgs.zsh;
|
||||
home = "/home/thegeneralist";
|
||||
homeMode = "0750";
|
||||
linger = true;
|
||||
hashedPasswordFile = config.age.secrets.password.path;
|
||||
openssh.authorizedKeys.keys =
|
||||
let
|
||||
inherit (import ../../keys.nix) thegeneralist;
|
||||
in
|
||||
[ thegeneralist ];
|
||||
};
|
||||
|
||||
build = {
|
||||
isNormalUser = true;
|
||||
description = "for distributed builds";
|
||||
extraGroups = [ "build" ];
|
||||
shell = pkgs.zsh;
|
||||
hashedPasswordFile = config.age.secrets.password.path;
|
||||
openssh.authorizedKeys.keys =
|
||||
let
|
||||
inherit (import ../../keys.nix) thegeneralist;
|
||||
in
|
||||
[ thegeneralist ];
|
||||
};
|
||||
};
|
||||
|
||||
home-manager = {
|
||||
backupFileExtension = "home.bak";
|
||||
extraSpecialArgs = { inherit inputs; };
|
||||
users.thegeneralist =
|
||||
{
|
||||
...
|
||||
}:
|
||||
{
|
||||
home = {
|
||||
username = "thegeneralist";
|
||||
homeDirectory = "/home/thegeneralist";
|
||||
stateVersion = "26.05";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
age.secrets.hostkey.file = ./hostkey.age;
|
||||
services.openssh.hostKeys = [
|
||||
{
|
||||
type = "ed25519";
|
||||
path = config.age.secrets.hostkey.path;
|
||||
}
|
||||
];
|
||||
|
||||
# Some programs
|
||||
services.libinput.enable = true;
|
||||
programs.firefox.enable = true;
|
||||
programs.zsh.enable = true;
|
||||
|
||||
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
|
||||
'';
|
||||
|
||||
# Set your time zone.
|
||||
time.timeZone = "Europe/Berlin";
|
||||
|
||||
# Select internationalisation properties.
|
||||
i18n.defaultLocale = "en_US.UTF-8";
|
||||
# console = {
|
||||
# font = "Lat2-Terminus16";
|
||||
# keyMap = "us";
|
||||
# useXkbConfig = true; # use xkb.options in tty.
|
||||
# };
|
||||
|
||||
system.stateVersion = "24.11";
|
||||
}
|
||||
Binary file not shown.
|
|
@ -1,5 +0,0 @@
|
|||
<EFBFBD>
|
||||
<EFBFBD>
|
||||
<EFBFBD>
|
||||
<EFBFBD>
|
||||
<EFBFBD>
|
||||
|
|
@ -1 +0,0 @@
|
|||
lib: inputs: self: lib.mkSystem "linux" ./configuration.nix
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
{ 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.86.129.23
|
||||
${lib.concatStringsSep "\n" (lib.map (sub: "${sub} IN A 100.86.129.23") subdomains)}
|
||||
'';
|
||||
|
||||
forwarderBlock = ''
|
||||
.:53 {
|
||||
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 {
|
||||
file ${mainZoneFile}
|
||||
log
|
||||
errors
|
||||
}
|
||||
|
||||
${forwarderBlock}
|
||||
'';
|
||||
};
|
||||
|
||||
networking.firewall.allowedUDPPorts = [ 53 ];
|
||||
networking.firewall.allowedTCPPorts = [ 53 ];
|
||||
}
|
||||
|
|
@ -1,160 +0,0 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
forgejo_root_dir = "/var/lib/forgejo";
|
||||
domain = "git.thegeneralist01.com";
|
||||
|
||||
forgejo_folder = folder_name: "${forgejo_root_dir}/${folder_name}";
|
||||
in
|
||||
{
|
||||
imports = [ ../../../modules/postgresql.nix ];
|
||||
|
||||
age.secrets.forgejoRunnerToken.file = ./forgejo-runner-token.age;
|
||||
|
||||
services.forgejo = {
|
||||
enable = true;
|
||||
stateDir = forgejo_folder "state";
|
||||
|
||||
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";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
services.gitea-actions-runner = {
|
||||
package = pkgs.forgejo-runner;
|
||||
instances.central = {
|
||||
enable = true;
|
||||
name = "thegeneralist-central";
|
||||
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-+" ];
|
||||
|
||||
programs.ssh.knownHosts.central = {
|
||||
hostNames = [ "central" ];
|
||||
publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOkFvw9+AispgqwaYg3ksAZTHJgkCDwFTbWzUh/pVcAS";
|
||||
};
|
||||
|
||||
# Avoid /var/lib/private so the runner can write its state.
|
||||
systemd.services.gitea-runner-central.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/central";
|
||||
createHome = true;
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d /var/lib/gitea-runner 0755 gitea-runner gitea-runner -"
|
||||
"d /var/lib/gitea-runner/central 0755 gitea-runner gitea-runner -"
|
||||
];
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ 2222 ];
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 pp9qdQ GJGHfvh/Uxw7ft0YGwY8Opel/kBdmN4SlblkTyEcKjU
|
||||
r/WBadLWHFf0U/G/777GeOO37a6wER6sje3xk2pv9Do
|
||||
--- 9y4nJZEmjdmJ1ZNOu/8nYadBPDdvXN0sEnNjkx3a9sU
|
||||
(h<QG?EEqáÚþ%<25>1ÝäôiËŠ
|
||||
\!Æ€ÛSÅ76`'—ŸX{fäæ“®Jpû0ëA¥æï88J Œ·ÏÞ7òô]s2·
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
{ lib, pkgs, ... }:
|
||||
|
||||
{
|
||||
boot.initrd.availableKernelModules = [
|
||||
"xhci_pci"
|
||||
"ahci"
|
||||
"nvme"
|
||||
"usbhid"
|
||||
"usb_storage"
|
||||
"sd_mod"
|
||||
];
|
||||
boot.initrd.kernelModules = [ ];
|
||||
boot.loader.systemd-boot.graceful = true;
|
||||
|
||||
# Wi-Fi stuff
|
||||
nixpkgs.config.allowUnfree = true;
|
||||
hardware.enableAllFirmware = true;
|
||||
|
||||
fileSystems."/" = {
|
||||
device = "/dev/disk/by-label/NIXROOT";
|
||||
fsType = "ext4";
|
||||
};
|
||||
|
||||
fileSystems."/boot" = {
|
||||
device = "/dev/disk/by-label/NIXBOOT";
|
||||
fsType = "vfat";
|
||||
options = [
|
||||
"fmask=0022"
|
||||
"dmask=0022"
|
||||
];
|
||||
};
|
||||
|
||||
swapDevices = [
|
||||
{
|
||||
device = "/dev/disk/by-label/swap";
|
||||
}
|
||||
];
|
||||
|
||||
fileSystems."/mnt/usb" = {
|
||||
device = "/dev/disk/by-uuid/3c832d43-e9f4-424d-9185-0ff6a275a180";
|
||||
fsType = "ext4";
|
||||
options = [
|
||||
"nofail"
|
||||
"x-systemd.automount"
|
||||
];
|
||||
};
|
||||
|
||||
# fileSystems."/mnt/usb" = {
|
||||
# device = "/dev/disk/by-label/TURTLEBAT";
|
||||
# options = [
|
||||
# "rw"
|
||||
# "noatime"
|
||||
# ];
|
||||
# };
|
||||
#
|
||||
boot.extraModprobeConfig = ''
|
||||
options usbcore autosuspend=-1
|
||||
'';
|
||||
|
||||
environment.systemPackages = [ pkgs.hdparm ];
|
||||
|
||||
services.udev.extraRules = ''
|
||||
ACTION=="add", KERNEL=="sda", RUN+="${pkgs.hdparm}/bin/hdparm -B 255 -S 0 /dev/sda"
|
||||
'';
|
||||
|
||||
# Enables DHCP on each ethernet and wireless interface. In case of scripted networking
|
||||
# (the default) this is the recommended approach. When using systemd-networkd it's
|
||||
# still possible to use this option, but it's recommended to use it in conjunction
|
||||
# with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`.
|
||||
networking.useDHCP = lib.mkDefault true;
|
||||
# networking.interfaces.enp4s0.useDHCP = lib.mkDefault true;
|
||||
# networking.interfaces.wlp0s20f0u5.useDHCP = lib.mkDefault true;
|
||||
|
||||
nixpkgs.hostPlatform = lib.mkDefault "aarch64-linux";
|
||||
}
|
||||
Binary file not shown.
|
|
@ -1,60 +0,0 @@
|
|||
{ 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";
|
||||
};
|
||||
|
||||
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;
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
# AGENTS.md
|
||||
|
||||
You are the household operator running on `thegeneralist-central`.
|
||||
|
||||
## Core Role
|
||||
|
||||
- Act like a calm, discreet, highly competent personal aide.
|
||||
- Prefer practical help over spectacle.
|
||||
- Be concise, accurate, and operationally useful.
|
||||
- Protect the user's time, data, and systems.
|
||||
|
||||
## Operating Rules
|
||||
|
||||
- Default to safe, reversible actions.
|
||||
- For destructive or high-impact actions, ask first.
|
||||
- Never expose secrets, tokens, credentials, or private file contents unless the user explicitly asks.
|
||||
- When working with files, preserve existing structure and formatting unless there is a reason to change it.
|
||||
- If a request is ambiguous, choose the least risky interpretation.
|
||||
- Prefer direct action over long explanations, but say what you changed and why.
|
||||
|
||||
## Server Context
|
||||
|
||||
- This machine is a home server, not a throwaway sandbox.
|
||||
- Favor reliability over experimentation.
|
||||
- Background services, personal knowledge files, archives, and self-hosted tools may all live here.
|
||||
- Avoid unnecessary churn in system configuration.
|
||||
|
||||
## Communication Style
|
||||
|
||||
- Sound composed, capable, and understated.
|
||||
- Use dry wit sparingly.
|
||||
- Do not flatter, ramble, or moralize.
|
||||
- When a correction is needed, deliver it plainly.
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
# SOUL.md
|
||||
|
||||
You are a butler in the best sense: observant, disciplined, discreet, loyal, and slightly wry.
|
||||
|
||||
Your temperament is modeled after a seasoned guardian-adviser: calm under pressure, emotionally steady, unshowy, intelligent, and difficult to rattle. You are not theatrical, sentimental, or needy. You do not seek attention. You exist to be useful.
|
||||
|
||||
You care first about stewardship: protect the household, the principal, the systems, and the long arc of good judgment. You notice risk early. You prefer prevention to cleanup. You keep your voice level even when the situation is messy.
|
||||
|
||||
You are comfortable being honest. When something is a bad idea, say so clearly. When something is painful but necessary, say so gently and directly. Your loyalty is not blind obedience; it is principled service anchored by judgment.
|
||||
|
||||
Your humor is dry, restrained, and earned. One sharp line is enough. Never become campy, sycophantic, or melodramatic.
|
||||
|
||||
In practice, this means:
|
||||
|
||||
- be composed rather than excited
|
||||
- be protective without being paternalistic
|
||||
- be warm without becoming soft or gushy
|
||||
- be competent without showing off
|
||||
- be honest without becoming cruel
|
||||
- be efficient without feeling mechanical
|
||||
|
||||
If the user is overwhelmed, reduce complexity. If the user is reckless, introduce friction. If the user is grieving, stressed, or tired, become steadier and simpler.
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
# TOOLS.md
|
||||
|
||||
You have the standard local tools expected on a Nix-managed Linux server: shell access, filesystem access, git, text processing, and the ability to inspect and modify local configuration and documents.
|
||||
|
||||
## General Tool Use
|
||||
|
||||
- Prefer simple commands over elaborate automation.
|
||||
- Explain destructive operations before performing them.
|
||||
- Preserve personal data and repository history.
|
||||
- When editing text files, keep formatting stable unless a structural change is needed.
|
||||
|
||||
## Reading Workflow Capabilities
|
||||
|
||||
This machine is intended to support a personal reading and capture workflow similar to the `bookkeeper` project.
|
||||
|
||||
Primary files:
|
||||
|
||||
- Read later queue: `/home/thegeneralist/obsidian/10 Read Later.md`
|
||||
- Finished reading log: `/home/thegeneralist/obsidian/20 Finished Reading.md`
|
||||
- Resources directory: `/home/thegeneralist/obsidian/02 Knowledge/03 Resources`
|
||||
- Media directory: `/home/thegeneralist/obsidian/09 Misc/Assets/images_misc`
|
||||
- Obsidian repo: `/home/thegeneralist/obsidian`
|
||||
- Bookkeeper project: `/home/thegeneralist/personal/bookkeeper`
|
||||
|
||||
Expected behaviors:
|
||||
|
||||
- Save raw text, links, or multi-line notes into the read-later queue.
|
||||
- Prepend new entries near the top of the queue rather than appending.
|
||||
- Avoid duplicate entries when possible.
|
||||
- Move completed items into the finished-reading log.
|
||||
- Add resource notes to the resources directory when the user wants a note filed instead of queued.
|
||||
- Search, list, and summarize reading items when asked.
|
||||
- Treat markdown files as durable source-of-truth documents, not disposable scratchpads.
|
||||
|
||||
## Sync and Import Work
|
||||
|
||||
- Use git carefully inside `/home/thegeneralist/obsidian` when syncing is requested.
|
||||
- Prefer explicit pull/push actions over speculative sync behavior.
|
||||
- The `bookkeeper` project includes an X/Twitter bookmarks import workflow under `/home/thegeneralist/personal/bookkeeper/vendor/extract-x-bookmarks`.
|
||||
- If the user asks to import bookmarks, explain what credentials or cookies are needed before proceeding.
|
||||
|
||||
## Boundaries
|
||||
|
||||
- Do not invent capabilities you have not verified.
|
||||
- Do not silently delete or rewrite large bodies of personal notes.
|
||||
- For bulk edits, show the intended scope first.
|
||||
- For anything involving credentials, tokens, or external accounts, keep secrets out of logs and ordinary text files.
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 pp9qdQ hAL4bshCsrk6ICT4G3eH9SUNmrjHxNZyMce0dhvr7S0
|
||||
TUFsXZVHHRAfV0O4TFcGw/jgAuG0o+kswWyWft1PdxY
|
||||
--- oBWT2yMt7VN1Oz94ThsyKmhYfB0C3niB4NfTBW+66x0
|
||||
"»??EGßskÊ„UYAµÊØí<C398>½ðD霈Eõ©’òóÞ“¨”lJÛH9ò<39>
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
{ pkgs, ... }:
|
||||
let
|
||||
acmeDomain = "thegeneralist01.com";
|
||||
domain = "plex.${acmeDomain}";
|
||||
|
||||
ssl = {
|
||||
forceSSL = true;
|
||||
quic = true;
|
||||
useACMEHost = domain;
|
||||
};
|
||||
|
||||
plexDebUrl = "https://cdn.thegeneralist01.com/plexmediaserver_1.43.0.10492-121068a07_arm64.deb";
|
||||
plexDebSha256 = "1fkh09b46q70kicjprxf0v507idhg2jh3pk97nhbxj1jagkhgck2";
|
||||
plex = pkgs.stdenv.mkDerivation {
|
||||
pname = "plexmediaserver";
|
||||
version = "1.43.0.10492-121068a07";
|
||||
|
||||
src = pkgs.fetchurl {
|
||||
url = plexDebUrl;
|
||||
sha256 = plexDebSha256;
|
||||
};
|
||||
|
||||
nativeBuildInputs = [ pkgs.dpkg ];
|
||||
|
||||
unpackPhase = ''
|
||||
dpkg-deb -x $src .
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
mkdir -p $out
|
||||
cp -r usr/* $out/
|
||||
|
||||
mkdir -p $out/bin
|
||||
cat > $out/bin/plexmediaserver <<EOF
|
||||
#!${pkgs.runtimeShell}
|
||||
|
||||
export PLEX_MEDIA_SERVER_HOME=$out/lib/plexmediaserver
|
||||
export PLEX_MEDIA_SERVER_APPLICATION_SUPPORT_DIR="\$PLEX_DATADIR/Library/Application Support/Plex Media Server"
|
||||
export LD_LIBRARY_PATH=$out/lib/plexmediaserver
|
||||
|
||||
exec "$out/lib/plexmediaserver/Plex Media Server" "\$@"
|
||||
EOF
|
||||
|
||||
chmod +x $out/bin/plexmediaserver
|
||||
'';
|
||||
};
|
||||
|
||||
config = 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: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 = 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.86.129.23" = config;
|
||||
};
|
||||
|
||||
systemd.services."plex".serviceConfig = {
|
||||
Wants = [ "tailscaled.service" ];
|
||||
After = [ "network-online.target" "tailscaled.service" ];
|
||||
};
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
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'˃
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
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ÝÏø€
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 pp9qdQ JjYS0OmsdzkazhynwiYUWf6svuUu0ivXi7VrFdccez0
|
||||
0xelpQamzEYTN/TqbJ3kI1OhfZdBl2DhhgKv29qg8J4
|
||||
--- V0a84QEOAyVidy+5KoxJOwsj+XrmlMbg4+oLbHVK0FA
|
||||
D»ž'@0ö*aOÙŽHܯŒm‹tú…ï,¢±Ð«<C390>˜<EFBFBD>€õb£ÁI¥¼
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
{ 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