Add Rupert’s configuration

This commit is contained in:
fruchti 2023-02-26 16:42:49 +01:00
commit 2b4264d32d
31 changed files with 1742 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
configuration.nix
hardware-configuration.nix
result
personal.nix

10
base/default.nix Normal file
View file

@ -0,0 +1,10 @@
{ ... }:
{
imports = [
./xkb
./neovim.nix
./packages.nix
./users.nix
./locale.nix
];
}

10
base/locale.nix Normal file
View file

@ -0,0 +1,10 @@
{ pkgs, lib, ... }:
{
time.timeZone = "Europe/Berlin";
i18n.defaultLocale = "en_GB.UTF-8";
console = {
font = "Lat2-Terminus16";
keyMap = "de";
};
}

134
base/neovim-init.vim Normal file
View file

@ -0,0 +1,134 @@
set nocompatible
set backspace=indent,eol,start
syntax enable
set mouse=a
set mousemodel=extend
set title
let g:airline_theme='term_light'
filetype plugin on
filetype indent on
" Enable spell check for commit messages
autocmd FileType gitcommit setlocal spell spelllang=en_gb
" Use spaces instead of tabs
set expandtab
" Be smart when using tabs ;)
set smarttab
" 1 tab == 4 spaces
set shiftwidth=4
set tabstop=4
set ai "Auto indent
set si "Smart indent
set wrap "Wrap lines
set nobackup
set nowb
set noswapfile
" Set to auto read when a file is changed from the outside
set autoread
" Leader
let mapleader = ","
let g:mapleader = ","
" Fast saving
nmap <leader>w :w!<cr>
" Save and start shell
nmap <leader># :w!<cr>:te<cr>i
"tnoremap <ESC> <C-\><C-n>:buffer #<CR>
tnoremap <C-c> <C-\><C-n>:buffer #<CR>
"autocmd TermClose * bd! " quit when a terminal closes instead of showing exit code and waiting
" Highlight search results
set hlsearch
" Makes search act like search in modern browsers
set incsearch
" Smart case when searching
set ignorecase
set smartcase
" Show results of pattern matching/replacing while typing
set inccommand=split
" When you press <leader>r you can search and replace the selected text
vnoremap <silent> <leader>r :call VisualSelection('replace')<CR>
" Disable highlight when <leader><cr> is pressed
map <silent> <leader><cr> :noh<cr>
" For regular expressions turn magic on
set magic
" Visual mode pressing * or # searches for the current selection
" Super useful! From an idea by Michael Naumann
vnoremap <silent> * :call VisualSelection('f')<CR>
vnoremap <silent> # :call VisualSelection('b')<CR>
" Show matching brackets when text indicator is over them
set showmatch
" How many tenths of a second to blink when matching brackets
set mat=2
" Spell checking
map <leader>ss :setlocal spell! spelllang=de_20<cr>
map <leader>se :setlocal spell! spelllang=en-curly_gb<cr>
map <leader>su :setlocal spell! spelllang=en-curly_us<cr>
" Treat long lines as break lines (useful when moving around in them)
map j gj
map k gk
" Smart way to move between windows
map <C-j> <C-W>j
map <C-k> <C-W>k
map <C-h> <C-W>h
map <C-l> <C-W>l
" More natural behaviour when splitting
set splitbelow
set splitright
" Useful mappings for managing tabs
map <leader>tn :tabnew<cr>
map <leader>to :tabonly<cr>
map <leader>tc :tabclose<cr>
map <leader>tm :tabmove
" Jump to particular tab directly
noremap <leader>1 1gt
noremap <leader>2 2gt
noremap <leader>3 3gt
noremap <leader>4 4gt
noremap <leader>5 5gt
noremap <leader>6 6gt
noremap <leader>7 7gt
noremap <leader>8 8gt
noremap <leader>9 9gt
noremap <leader>0 :tablast<cr>
" Switch CWD to the directory of the open buffer
map <leader>cd :cd %:p:h<cr>:pwd<cr>
" Remember info about open buffers on close
set viminfo^=%
" Toggle paste mode on and off
map <leader>pp :setlocal paste!<cr>
" Save and make
nnoremap <leader>m :wa <BAR> :Make<CR>
hi ColorColumn ctermbg=47
let g:vimtex_compiler_enabled = 0
let g:local_vimrc = {'names': ['.local.vimrc'], 'hash_fun': 'LVRHashOfFile'}

28
base/neovim.nix Normal file
View file

@ -0,0 +1,28 @@
{ pkgs, ... }:
{
programs.neovim = {
enable = true;
vimAlias = true;
viAlias = true;
defaultEditor = true;
configure = {
customRC = (builtins.readFile ./neovim-init.vim);
packages.myVimPackage = with pkgs.vimPlugins; {
start = [
vim-lastplace
direnv-vim
vim-addon-local-vimrc
vim-nix
vim-airline
vim-airline-themes
vim-colorschemes
changeColorScheme-vim
vim-dispatch
vimtex
suda-vim
];
opt = [];
};
};
};
}

46
base/packages.nix Normal file
View file

@ -0,0 +1,46 @@
{ config, pkgs, lib, ... }:
{
environment.systemPackages = with pkgs; [
direnv nix-direnv
tmux zellij
wget
rsync
git
gnupg
file
ripgrep
fd
htop
ncdu
ranger nnn joshuto
hexyl
rink
kitty
kitty-themes
] ++ lib.optionals config.services.xserver.enable [
wine
pavucontrol
xsensors
kitty
kitty-themes
firefox
ungoogled-chromium
zathura
gthumb
vlc
feh
xsel
];
fonts.fonts = with pkgs; [
vollkorn
alegreya alegreya-sans
b612
raleway
];
}

19
base/users.nix Normal file
View file

@ -0,0 +1,19 @@
{ pkgs, lib, ... }:
let
definedInPersonalDotNix = lib.mkDefault (throw "Configuration option missing from personal.nix");
in
{
users.users = {
fruchti = {
isNormalUser = true;
extraGroups = [ "wheel" "networkmanager" "audio" "video" ];
openssh.authorizedKeys.keys = definedInPersonalDotNix;
shell = pkgs.fish;
};
};
users.extraGroups = {
system = {
members = [ "fruchti" ];
};
};
}

16
base/xkb/default.nix Normal file
View file

@ -0,0 +1,16 @@
{ ... }:
{
services.xserver.layout = "us-fruchti";
services.xserver.extraLayouts = {
de-fruchti = {
description = "DE layout with some small changes";
languages = [ "deu" ];
symbolsFile = ./symbols/de-fruchti;
};
us-fruchti = {
description = "US-altgr-intl layout with some small changes";
languages = [ "eng" ];
symbolsFile = ./symbols/us-fruchti;
};
};
}

View file

@ -0,0 +1,8 @@
xkb_symbols "de-fruchti"
{
include "de(basic)"
// Swap insert/print screen
key <PRSC> { [ Insert ] };
key <INS> { [ Print ] };
};

View file

@ -0,0 +1,44 @@
partial alphanumeric_keys
xkb_symbols "us-fruchti" {
include "us(altgr-intl)"
name[Group1]="English (intl., with AltGr dead keys, customised)";
key <AC02> {
// Change: Replace section with U1E9E (capital sharp s)
[ s, S, ssharp, U1E9E ]
};
key <AC04> {
// Change: Replace f with U017F (long s)
// Change: Replace F with section
[ f, F, U017F, section ]
};
key <AC05> {
// Change: replace g with doublelowquotemark
// Change: replace G with singlelowquotemark
[ g, G, doublelowquotemark, singlelowquotemark ]
};
key <AC06> {
// Change: replace h with leftdoublequotemark
// Change: replace H with leftsinglequotemark
[ h, H, leftdoublequotemark, leftsinglequotemark ]
};
key <AC08> {
// oe/OE are already available with AltGr+(Shift+)X
// Change: Replace oe with endash
// Change: Replace OE with emdash
[ k, K, endash, emdash ]
};
key <AB05> {
// Change: Replace b with U2026 (ellipsis)
// Change: Replace B with Greek_OMEGA
[ b, B, U2026, Greek_OMEGA ]
};
key <AB07> {
// Change: Replace mu with endash
[ m, M, mu, endash ]
};
key <SPCE> {
// Change: Add thinspace for AltGr+Space
[ space, space, U2009, nobreakspace ]
};
};

119
hosts/Rupert.nix Normal file
View file

@ -0,0 +1,119 @@
{ config, lib, pkgs, ... }:
let
definedInPersonalDotNix = lib.mkDefault (throw "Configuration option missing from personal.nix");
in
{
imports = [
./nextcloud.nix
./dyndns.nix
./adguard.nix
./mpd.nix
./burp-server.nix
./hedgedoc.nix
./transcode.nix
./development.nix
./bspwm.nix
# ./open-pgsql.nix
];
users.users = {
waldi = {
isNormalUser = true;
extraGroups = [ "audio" ];
shell = pkgs.fish;
openssh.authorizedKeys.keys = definedInPersonalDotNix;
};
};
users.extraGroups = {
pulse-access = {
members = [ "waldi" "fruchti" ];
};
music = {
members = [ "fruchti" ];
};
};
environment.systemPackages = with pkgs; [
ntfsprogs
texlive.combined.scheme-full
ncmpcpp
];
services.burp.client = {
enable = true;
password = config.services.burp.server.clients."${config.networking.hostName}".password;
};
# Flatpak
services.flatpak.enable = true;
xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-gtk ];
xdg.portal.enable = true;
hardware.bluetooth = {
enable = true;
};
# Some programs need SUID wrappers, can be configured further or are
# started in user sessions.
# programs.mtr.enable = true;
programs.gnupg.agent = {
enable = true;
enableSSHSupport = true;
};
# List services that you want to enable:
# Enable the OpenSSH daemon.
services.openssh = {
enable = true;
forwardX11 = true;
passwordAuthentication = false;
};
services.avahi.enable = true;
networking.firewall.allowedTCPPorts = [
22
1935 # RTMP
4971 # BURP
];
# Copy the NixOS configuration file and link it from the resulting system
# (/run/current-system/configuration.nix). This is useful in case you
# accidentally delete configuration.nix.
# system.copySystemConfiguration = true;
system.autoUpgrade.enable = true;
system.autoUpgrade.allowReboot = false;
system.autoUpgrade.sendEmail = true;
# systemd.services.nixos-upgrade.onFailure = lib.mkIf config.system.autoUpgrade.enable [ "status-email@%n.service" ];
nix.gc = {
automatic = true;
dates = "weekly";
options = "--delete-older-than 30d";
};
services.btrfsScrub = {
enable = true;
paths = {
"/" = {
onCalendar = "*-*-* 02:00:00";
};
"/data" = {
onCalendar = "Thu *-*-* 02:00:00";
};
};
};
security.acme = {
defaults = {
email = config.email.adminEmail;
};
acceptTerms = true;
};
services.udev.extraRules = ''
SUBSYSTEM=="video4linux", ATTRS{idProduct}=="0002", ATTRS{idVendor}=="1d6b", SYMLINK+="hdmi_capture"
'';
}

28
hosts/adguard.nix Normal file
View file

@ -0,0 +1,28 @@
{ config, pkgs, ... }:
{
services.adguardhome = {
enable = true;
};
services.nginx = {
enable = true;
virtualHosts = {
"ad.guard" = {
listenAddresses = [ "0.0.0.0" "[::]" ];
locations."/" = {
proxyPass = "http://localhost:5380";
};
};
};
};
networking.firewall.allowedUDPPorts = [ 53 67 68 ];
# Capabilities for DHCP server
systemd.services.adguardhome.serviceConfig.AmbientCapabilities = [ "CAP_NET_RAW" ];
systemd.services."adguardhome" = {
# requires = ["dhcpcd.service"];
after = ["dhcpcd.service"];
};
}

22
hosts/bspwm.nix Normal file
View file

@ -0,0 +1,22 @@
{ config, pkgs, ... }:
{
services.xserver = {
enable = true;
windowManager.bspwm.enable = true;
displayManager = {
defaultSession = "none+bspwm";
lightdm.enable = true;
autoLogin.enable = true;
autoLogin.user = "waldi";
};
};
services.unclutter-xfixes = {
enable = true;
};
environment.systemPackages = with pkgs; [
rofi
ranger
];
}

38
hosts/burp-server.nix Normal file
View file

@ -0,0 +1,38 @@
{ config, lib, ... }:
let
definedInPersonalDotNix = lib.mkDefault (throw "Configuration option missing from personal.nix");
in
{
services.burp.server = {
enable = true;
dataDirectory = "/data/burp";
sslKeyPassword = definedInPersonalDotNix;
workingDirRecoveryMethod = "resume";
maxResumeAttempts = 3;
keep = [ 14 4 6 2 ];
clients = {
${config.networking.hostName} = {
password = definedInPersonalDotNix;
};
Pullach = {
password = definedInPersonalDotNix;
};
Disco = {
password = definedInPersonalDotNix;
};
Berthold = {
password = definedInPersonalDotNix;
};
Ernesto = {
password = definedInPersonalDotNix;
};
};
superClients = [
config.networking.hostName
];
timerArgs = [
"20h"
"Mon,Tue,Wed,Thu,Fri,Sat,Sun,00,01,02,03,04,05,06,07,08,17,18,19,20,21,22,23"
];
};
}

14
hosts/development.nix Normal file
View file

@ -0,0 +1,14 @@
{ config, pkgs, ... }:
{
environment.systemPackages = with pkgs; [
picocom
usbutils # lsusb etc.
binwalk
];
users.extraGroups = {
plugdev = {
members = [ "fruchti" ];
};
};
}

54
hosts/dyndns.nix Normal file
View file

@ -0,0 +1,54 @@
{ config, pkgs, lib, ... }:
let
definedInPersonalDotNix = lib.mkDefault (throw "Configuration option missing from personal.nix");
getipv6 = pkgs.writeText "getipv6.sh" ''
${pkgs.nettools}/bin/ifconfig enp3s0 | sed -n -E 's/^\ *inet6 (2001(:[0-9a-f]+)+)\ .*$/\1/p'
'';
in
{
networking.tempAddresses = "disabled";
# networking.interfaces.enp3s0 = {
# tempAddress = "disabled";
# ipv4.addresses = [{
# address = "192.168.178.43";
# prefixLength = 24;
# }];
# };
networking.defaultGateway = "192.168.178.1";
networking.nameservers = [ "9.9.9.9" "8.8.8.8" ];
networking.dhcpcd = {
enable = true;
persistent = true;
extraConfig = ''
duid
vendorclassid
slaac hwaddr
noipv4ll
#ia_pd 1 internal
interface enp3s0
static ip_address=192.168.178.43/24
static routers=192.168.178.1
static domain_name_servers=192.168.178.1 8.8.8.8
ia_pd
'';
};
services.ddclient = {
enable = true;
verbose = true;
use = "cmd, cmd='${pkgs.bash}/bin/bash ${getipv6}'";
domains = [
((lib.toLower config.networking.hostName) + ".gvfr.de")
];
ipv6 = true;
server = definedInPersonalDotNix;
username = definedInPersonalDotNix;
passwordFile = "/secrets/dyndns_password_${config.services.ddclient.username}.txt";
extraConfig = ''
wildcard=no
'';
};
}

79
hosts/hedgedoc.nix Normal file
View file

@ -0,0 +1,79 @@
{ config, lib, pkgs, ... }:
let
domain = "md.gvfr.de";
in
{
services.hedgedoc = {
enable = true;
workDir = "/data/hedgedoc";
settings = {
port = 7000;
domain = domain;
protocolUseSSL = true;
uploadsPath = "/data/hedgedoc/uploads";
allowGravatar = false;
db = {
dialect = "postgres";
host = "/run/postgresql";
username = "hedgedoc";
database = "hedgedoc";
};
allowAnonymous = false;
allowAnonymousEdits = true;
csp = {
enable = true;
directives = {
scriptSrc = "gvfr.de";
};
upgradeInsecureRequest = "auto";
addDefaults = true;
};
allowEmailRegister = false;
};
};
services.postgresql = {
enable = true;
ensureUsers = [
{
name = "hedgedoc";
ensurePermissions = {
"DATABASE hedgedoc" = "ALL PRIVILEGES";
};
}
];
ensureDatabases = [ "hedgedoc" ];
};
services.nginx = {
enable = true;
virtualHosts.${domain} = {
listenAddresses = [ "0.0.0.0" "[::]" ];
forceSSL = true;
enableACME = true;
locations = {
"/" = {
proxyPass = "http://localhost:7000";
extraConfig = ''
proxy_set_header Host $host;
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;
'';
};
"/socket.io/" = {
proxyPass = "http://localhost:7000";
extraConfig = ''
proxy_set_header Host $host;
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 Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
'';
};
};
};
};
}

63
hosts/mpd.nix Normal file
View file

@ -0,0 +1,63 @@
{ config, pkgs, ... }:
let
httpStreamPort = 8000;
in
{
services.mpd = {
enable = true;
musicDirectory = "/data/music/flac";
playlistDirectory = "/data/music/playlists";
network.listenAddress = "any";
extraConfig = ''
audio_output {
type "pulse"
name "Local Music Player Daemon"
server "127.0.0.1"
mixer_type "software"
}
audio_output {
type "fifo"
name "fft"
path "/tmp/mpd.fifo"
format "44100:16:2"
}
audio_output {
type "httpd"
name "HTTP-Stream (Port 8000)"
encoder "vorbis" # optional
bind_to_address "0.0.0.0"
port "${toString httpStreamPort}"
# quality "5.0" # do not define if bitrate is defined
bitrate "128" # do not define if quality is defined
format "48000:16:1"
always_on "yes" # prevent MPD from disconnecting all listeners when playback is stopped.
tags "yes" # httpd supports sending tags to listening streams.
}
'';
};
hardware.pulseaudio = {
enable = true;
systemWide = true;
tcp.enable = true;
tcp.anonymousClients.allowedIpRanges = [ "127.0.0.1" ];
};
users.extraGroups.pulse-access = {
members = [ "mpd" ];
};
# users.extraGroups.music = {
# members = [ "mpd" ];
# };
# Workaround https://github.com/NixOS/nixpkgs/issues/114399
system.activationScripts.fix-pulse-permissions = ''
chmod 755 /run/pulse
'';
environment.systemPackages = with pkgs; [ mpc-cli ];
networking.firewall.allowedTCPPorts = [ config.services.mpd.network.port httpStreamPort ];
}

62
hosts/nextcloud.nix Normal file
View file

@ -0,0 +1,62 @@
{ config, lib, pkgs, ... }:
let
hostName = (lib.toLower config.networking.hostName) + ".gvfr.de";
in
{
services.nextcloud = {
enable = true;
https = true;
package = pkgs.nextcloud25;
hostName = hostName;
datadir = "/data/nextcloud";
config = {
dbtype = "pgsql";
dbhost = "/run/postgresql";
adminpassFile = "/secrets/nextcloud_admin_password.txt";
extraTrustedDomains = [
((lib.toLower config.networking.hostName) + ".lan")
(lib.toLower config.networking.hostName)
];
};
caching.redis = true;
enableBrokenCiphersForSSE = false;
};
services.postgresql = {
enable = true;
ensureUsers = [
{
name = "nextcloud";
ensurePermissions = {
"DATABASE nextcloud" = "ALL PRIVILEGES";
};
}
{
name = "superuser";
ensurePermissions = {
"ALL TABLES IN SCHEMA public" = "ALL PRIVILEGES";
};
}
];
ensureDatabases = [ "nextcloud" ];
};
# Ensure that postgres is running *before* running the setup
systemd.services."nextcloud-setup" = {
requires = ["postgresql.service"];
after = ["postgresql.service"];
};
networking.firewall.allowedTCPPorts = [ 80 443 ];
services.nginx = {
virtualHosts.${hostName} = {
forceSSL = true;
enableACME = true;
};
};
users.extraGroups.music = {
members = [ "nextcloud" ];
};
}

11
hosts/open-pgsql.nix Normal file
View file

@ -0,0 +1,11 @@
{ ... }:
{
# Open PostgreSQL port for data transfer
services.postgresql = {
enableTCPIP = true;
authentication = ''
host all all 192.168.178.0/24 trust
'';
};
networking.firewall.allowedTCPPorts = [ 5432 ];
}

View file

@ -0,0 +1,42 @@
{ pkgs, lib, config, ... }:
{
systemd.services.reboot-check = {
description = "Check if the system needs a reboot";
onFailure = [ "status-email@%n.service" ];
serviceConfig = {
Type = "oneshot";
};
script = ''
#!${pkgs.bash}/bin/bash
booted="$(readlink /run/booted-system/{initrd,kernel,kernel-modules})"
built="$(readlink /nix/var/nix/profiles/system/{initrd,kernel,kernel-modules})"
indent()
{
while read line ; do
echo " $line"
done <<< "$1"
}
if [ "$booted" != "$built" ] ; then
echo "Booted kernel version"
indent "$booted"
echo "does not match currently active"
indent "$built"
echo "A reboot is required."
exit 1
fi
'';
};
systemd.timers.reboot-check = {
wantedBy = [ "timers.target" ];
enable = true;
timerConfig = {
Unit = "reboot-check.service";
OnCalendar = "*-*-* 18:30:00";
Persistent = true;
};
};
}

33
hosts/transcode.nix Normal file
View file

@ -0,0 +1,33 @@
{ config, pkgs, ... }:
let
transcode = pkgs.callPackage ../packages/transcode.nix {};
flacPath = "/data/music/flac";
mp3Path = "/data/music/mp3";
oggPath = "/data/music/ogg";
in
{
systemd.services.transcode = {
description = "Transcode music form FLAC to MP3 and OGG";
onFailure = [ "status-email@%n.service" ];
serviceConfig = {
Type = "simple";
ExecStart = "${transcode}/bin/transcode --mp3-out \"${mp3Path}\" --ogg-out \"${oggPath}\" \"${flacPath}\"";
DynamicUser = true;
Group = "music";
UMask = "002";
ReadOnlyDirectories = [ flacPath ];
ReadWriteDirectories = [ mp3Path oggPath ];
Nice = 19;
NoNewPrivileges = true;
PrivateTmp = true;
PrivateDevices = true;
PrivateUsers = true;
ProtectClock = true;
ProtectSystem = "strict";
ProtectHome = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectProc = "invisible";
};
};
}

164
options/auto-upgrade.nix Normal file
View file

@ -0,0 +1,164 @@
{ pkgs, lib, config, ... }:
with lib;
let
fromAddress = config.email.fromAddress;
fromIdentity = config.email.fromIdentity;
toAddress = config.email.adminEmail;
cfg = config.system.autoUpgrade;
in
{
options.system.autoUpgrade = {
sendEmail = mkOption {
type = types.bool;
default = false;
description = mdDoc ''
Whether to send a status email after an upgrade.
'';
};
};
config = mkIf cfg.enable {
systemd.services.nixos-upgrade.script = mkOverride 50 (
let
nixos-rebuild = "${config.system.build.nixos-rebuild}/bin/nixos-rebuild";
date = "${pkgs.coreutils}/bin/date";
readlink = "${pkgs.coreutils}/bin/readlink";
grep = "${pkgs.gnugrep}/bin/grep";
shutdown = "${config.systemd.package}/bin/shutdown";
sendmail = "${pkgs.system-sendmail}/bin/sendmail";
upgradeFlag = optional (cfg.channel == null) "--upgrade";
in ''
set -o pipefail
upgrade() {
${nixos-rebuild} ${cfg.operation} ${toString (cfg.flags ++ upgradeFlag)}
}
indent()
{
while read -r line ; do
echo " $line"
done <<< "$1"
}
start_time="$(${date})"
reboot_allowed="no"
do_reboot="no"
exit_code=0
${optionalString cfg.allowReboot ''
reboot_allowed="yes"
${optionalString (cfg.rebootWindow != null) ''
current_time="$(${date} +%H:%M)"
lower="${cfg.rebootWindow.lower}"
upper="${cfg.rebootWindow.upper}"
if [[ "''${lower}" < "''${upper}" ]]; then
if [[ "''${current_time}" > "''${lower}" ]] && \
[[ "''${current_time}" < "''${upper}" ]]; then
reboot_allowed="yes"
else
reboot_allowed="no"
fi
else
# lower > upper, so we are crossing midnight (e.g. lower=23h, upper=6h)
# we want to reboot if cur > 23h or cur < 6h
if [[ "''${current_time}" < "''${upper}" ]] || \
[[ "''${current_time}" > "''${lower}" ]]; then
reboot_allowed="yes"
else
reboot_allowed="no"
fi
fi
''}
''}
output_file="$(mktemp)"
upgrade 2>&1 | tee "$output_file" || exit_code=$?
upgrade_output="$(cat "$output_file")"
rm -f "$output_file"
send_email=no
email_subject="Upgrade succeeded"
email_body="The system upgrade started at $start_time has succeeded."
if [ "$exit_code" -ne 0 ] ; then
send_email=yes
email_subject="Upgrade failed (exit code $exit_code)"
email_body="The system upgrade started at $start_time has failed with exit code $exit_code."
fi
possible_warnings="$(${grep} -e "^trace:" <<<"$upgrade_output" || true)"
if [ "$possible_warnings" != "" ] ; then
send_email=yes
email_subject="$email_subject with warnings"
email_body="$(cat <<-EOF
$email_body
These trace messages and warnings were encountered:
---------------------------------------------------
$possible_warnings
EOF
)"
fi
booted_version="$(${readlink} /run/booted-system/{initrd,kernel,kernel-modules})"
built_version="$(${readlink} /nix/var/nix/profiles/system/{initrd,kernel,kernel-modules})"
if [ "$booted_version" != "$built_version" ] ; then
version_comparison="$(cat <<-EOF
The booted kernel version
$(indent "$booted_version")
does not match currently active
$(indent "$built_version")
.
EOF
)"
echo "$version_comparison"
send_email=yes
email_subject="$email_subject, reboot required"
email_body="$(cat <<-EOF
$email_body
A reboot is required, because:
------------------------------
$version_comparison
EOF
)"
if [ "$reboot_allowed" = "yes" ] && [ $exit_code -eq 0 ] ; then
email_body="$(printf "%s\n%s" "$email_body" "The system will reboot now.")"
do_reboot="yes"
fi
fi
${optionalString cfg.sendEmail ''
if [ "$send_email" = "yes" ] ; then
${sendmail} -t -X - <<-EOF
To: ${toAddress}
From: ${fromIdentity}
Subject: $email_subject
Content-Transfer-Encoding: 8bit
Content-Type: text/plain; charset=UTF-8
X-Priority: 3
$email_body
Full upgrade output:
--------------------
$upgrade_output
EOF
fi
''}
if [ "$do_reboot" = "yes" ] ; then
echo "Rebooting system."
${shutdown} -r +1
fi
exit $exit_code
''
);
};
}

56
options/btrfs-scrub.nix Normal file
View file

@ -0,0 +1,56 @@
{ pkgs, utils, lib, config, ... }:
with lib;
let
cfg = config.services.btrfsScrub;
in
{
options.services.btrfsScrub = {
enable = mkEnableOption "Periodically run btrfs-scrub on filesystems";
enableFailureEmail = mkOption {
type = types.bool;
default = true;
description = mdDoc "Send e-mail on scrub failure";
};
paths = mkOption {
type = with types; attrsOf (submodule {
options = {
onCalendar = mkOption {
type = str;
default = "*-*-* 02:00:00";
};
};
});
};
};
config.services.statusEmail.enable = mkIf (cfg.enable && cfg.enableFailureEmail) true;
config.systemd.services."btrfs-scrub@" = mkIf cfg.enable {
unitConfig = {
Description = "Scrub btrfs volume %I";
OnFailure = mkIf cfg.enableFailureEmail "status-email@%n.service";
};
serviceConfig = {
Type = "simple";
ExecStart = "${pkgs.btrfs-progs}/bin/btrfs scrub start -B -d %f";
# ExecStop = "-${pkgs.btrfs-progs}/bin/btrfs scrub cancel %f";
Nice = 19;
};
};
config.systemd.timers = mkIf cfg.enable (mapAttrs' (name: value:
let
pathEscaped = utils.escapeSystemdPath name;
in
nameValuePair "btrfs-scrub-${pathEscaped}"
{
wantedBy = [ "timers.target" ];
enable = true;
timerConfig = {
Unit = "btrfs-scrub@${pathEscaped}.service";
OnCalendar = value.onCalendar;
RandomizedDelaySec = "1h";
Persistent = true;
};
}) cfg.paths);
}

432
options/burp.nix Normal file
View file

@ -0,0 +1,432 @@
{ config, lib, pkgs, ... }:
with lib;
let
libDir = "/var/lib/burp";
clientCertDir = "${libDir}/CA-client";
cfg = config.services.burp;
clientConf = pkgs.writeText "burp.conf" ''
mode = client
pidfile = /run/burp/burp.pid
port = ${toString cfg.client.port}
status_port = ${toString cfg.client.statusPort}
server = ${cfg.client.server}
password = ${cfg.client.password}
cname = ${cfg.client.clientName}
ca_burp_ca = ${cfg.package}/bin/burp_ca
ca_csr_dir = ${clientCertDir}
ssl_cert_ca = ${libDir}/ssl_cert_ca.pem
ssl_cert = ${libDir}/ssl_cert-client.pem
ssl_key = ${libDir}/ssl_cert-client.key
ssl_key_password = ${cfg.client.sslKeyPassword}
ssl_peer_cn = burpserver
${concatMapStringsSep "\n" (x: "include = " + x) cfg.client.includes}
${concatMapStringsSep "\n" (x: "exclude = " + x) cfg.client.excludes}
nobackup = .nobackup
${cfg.client.extraConfig}
'';
serverHome = "/var/lib/burp";
serverClientConfDir = serverHome + "/clientconfdir";
serverCaConf = pkgs.writeText "CA.cnf" ''
CA_DIR = ${serverHome}/CA
[ ca ]
dir = $ENV::CA_DIR
database = $dir/index.txt
serial = $dir/serial.txt
certs = $dir/certs
new_certs_dir = $dir/newcerts
crlnumber = $dir/crlnumber.txt
unique_subject = no
default_md = sha256
default_days = 7300
default_crl_days = 7300
#????
name_opt = ca_default
cert_opt = ca_default
x509_extensions = usr_cert
copy_extensions = copy
policy = policy_anything
[ usr_cert ]
basicConstraints = CA:FALSE
[ policy_anything ]
commonName = supplied
'';
serverConf = pkgs.writeText "burp-server.conf" ''
mode = server
pidfile = /run/burp/burp-server.pid
listen = ${cfg.server.listen}
max_children = 5;
listen_status = ${cfg.server.listenStatus}
max_status_children = 5;
user = burp
group = burp
umask = 0022
clientconfdir = ${serverClientConfDir}
directory = ${cfg.server.dataDirectory}
ca_conf = ${serverCaConf}
ca_name = burpCA
ca_server_name = burpserver
ca_burp_ca = ${cfg.package}/bin/burp_ca
ca_crl_check = 1
ssl_cert_ca = ${serverHome}/ssl_cert_ca.pem
ssl_cert = ${serverHome}/ssl_cert-server.pem
ssl_key = ${serverHome}/ssl_cert-server.key
ssl_dhfile = ${serverHome}/dhfile.pem
ssl_key_password = ${cfg.server.sslKeyPassword}
${concatMapStringsSep "\n" (x: "keep = " + toString x) cfg.server.keep}
timer_script = ${cfg.server.timerScript}
${concatMapStringsSep "\n" (x: "timer_arg = " + x) cfg.server.timerArgs}
dedup_group = global
working_dir_recovery_method = ${cfg.server.workingDirRecoveryMethod}
max_resume_attempts = ${toString cfg.server.maxResumeAttempts}
${concatMapStringsSep "\n" (x: "super_client = " + x) cfg.server.superClients}
${concatMapStringsSep "\n" (x: "restore_client = " + x) cfg.server.restoreClients}
${cfg.server.extraConfig}
'';
clientConfigs = lib.attrsets.mapAttrs (name: config: (pkgs.writeText name ''
password = ${config.password}
${config.extraConfig}
'')) cfg.server.clients;
in {
options = {
services.burp.package = mkOption {
type = types.package;
default = pkgs.burp;
description = ''
The package which is used as a burp server/client.
'';
};
services.burp.server = {
enable = mkEnableOption "Burp server";
listen = mkOption {
type = types.str;
default = ":::4971";
description = ''
Listen address used for backup communication.
'';
};
listenStatus = mkOption {
type = types.str;
default = ":::4972";
description = ''
Listen address used for querying backup and server status.
'';
};
dataDirectory = mkOption {
type = types.str;
default = "/var/spool/burp";
description = ''
Where the backup data is stored.
'';
};
sslKeyPassword = mkOption {
type = types.str;
default = "change-this-password";
description = ''
SSL key password for loading a certificate with encryption.
'';
};
keep = mkOption {
type = types.listOf types.int;
default = [ 7 ];
description = ''
How many backups to keep.
'';
};
timerScript = mkOption {
type = types.str;
default = "${cfg.package}/share/burp/scripts/timer_script";
description = ''
Timer script used to evaluate if it's backup time.
'';
};
timerArgs = mkOption {
type = types.listOf types.str;
default = [
"20h"
"Mon,Tue,Wed,Thu,Fri,00,01,02,03,04,05,19,20,21,22,23"
"Sat,Sun,00,01,02,03,04,05,06,07,08,17,18,19,20,21,22,23"
];
description = ''
How often should backups be triggered.
'';
};
superClients = mkOption {
type = types.listOf types.str;
default = [];
description = ''
Clients that are permitted to list, verify, restore, delete, and diff files belonging to any other client, according to the super_client's client_can permissions (eg, 'client_can_list'). If this is too permissive, you may set a super_client for individual original clients in the individual clientconfdir files, or look at the 'restoreClients' option.
'';
};
restoreClients = mkOption {
type = types.listOf types.str;
default = [];
description = ''
Clients that are permitted to list, verify, restore, delete, and diff files belonging to any other client, according to the client_can permissions on both the restore_client and the original_client (eg, 'client_can_list'). If this is too permissive, you may set a restoreClient for individual original clients in the individual clientconfdir files.
'';
};
workingDirRecoveryMethod = mkOption {
type = types.str;
default = "delete";
description = ''
This option tells the server what to do when it finds the working directory of an interrupted backup (perhaps somebody pulled the plug on the server, or something). This can be overridden by the client configurations files in clientconfdir on the server. Options are...
delete: Just delete the old working directory.
resume: Continue the previous backup from the point at which it left off. NOTE: If the client has changed its include/exclude configuration since the backup was interrupted, the recovery method will automatically switch to 'delete'. See also the 'resume attempts' option.
'';
};
maxResumeAttempts = mkOption {
type = types.int;
default = 0;
description = ''
If workingDirRecoveryMethod is 'resume', this option tells the server how many times to attempt to resume before giving up and deleting the working directory. The default is 0, which means to never give up.
'';
};
clients = mkOption {
type = types.attrsOf (types.submodule ({ ... }: {
options = {
password = mkOption {
type = types.str;
default = "change-this-password";
description = ''
Password used by the client for first contact with the server.
'';
};
extraConfig = mkOption {
type = types.lines;
default = "";
description = ''
Extra configuration for burp client. Contents will be added verbatim to the
configuration file.
'';
};
};
}));
};
extraConfig = mkOption {
type = types.lines;
default = "";
description = ''
Extra configuration for burp client. Contents will be added verbatim to the
configuration file.
'';
};
};
services.burp.client = {
enable = mkEnableOption "Burp client";
frequency = mkOption {
type = types.str;
default = "hourly";
description = ''
Controls how frequently the systemd timer is triggered and a backup
is attempted.
Backup frequency is still determined by the server, this
controls only how often the client tries.
'';
};
server = mkOption {
type = types.str;
default = "127.0.0.1";
description = ''
Address of burp server the client should contact.
'';
};
port = mkOption {
type = types.port;
default = 4971;
description = ''
Port used for backup communication.
'';
};
statusPort = mkOption {
type = types.port;
default = 4972;
description = ''
Port used for querying backup and server status.
'';
};
clientName = mkOption {
type = types.str;
default = "${config.networking.hostName}";
description = ''
Name the client should use to identify itself to the server.
'';
};
password = mkOption {
type = types.str;
default = "change-this-password";
description = ''
Password used by the client for first contact with the server.
'';
};
sslKeyPassword = mkOption {
type = types.str;
default = "change-this-password";
description = ''
SSL key password for loading a certificate with encryption.
'';
};
includes = mkOption {
type = types.listOf types.str;
default = [ "/etc" "/root" "/var" "/home" ];
description = ''
List of locations to include in backups.
'';
};
excludes = mkOption {
type = types.listOf types.str;
default = [ ];
description = ''
List of locations to exclude from the backup.
'';
};
extraConfig = mkOption {
type = types.lines;
default = "";
description = ''
Extra configuration for burp client. Contents will be added verbatim to the
configuration file.
'';
};
};
};
config = {
environment.systemPackages = mkIf cfg.client.enable [ cfg.package ];
systemd.timers.burp-client = mkIf cfg.client.enable {
description = "Timer for triggering Burp client and start a backup";
wantedBy = [ "timers.target" ];
timerConfig = {
OnCalendar = cfg.client.frequency;
Unit = "burp-client.service";
};
};
systemd.services.burp-client = mkIf cfg.client.enable {
description = "Burp client";
after = [ "network.target" ];
path = [ cfg.package pkgs.nettools pkgs.openssl ];
preStart = ''
if [ ! -d "${libDir}" ]; then
mkdir -m 0755 -p ${libDir}
mkdir -m 0700 -p ${clientCertDir}
${cfg.package}/bin/burp -c ${libDir}/burp.conf -g
fi
ln -f -s ${clientConf} ${libDir}/burp.conf
'';
serviceConfig = {
Type = "simple";
ExecStart = "${cfg.package}/bin/burp -c ${libDir}/burp.conf -a t";
SuccessExitStatus = "3";
};
};
users = mkIf cfg.server.enable {
users.burp = {
group = "burp";
uid = 497;
home = "${serverHome}";
createHome = true;
description = "Burp Server user";
shell = "${pkgs.bash}/bin/bash";
isSystemUser = true;
};
groups.burp = {
gid = 497;
};
};
systemd.services.burp-server = mkIf cfg.server.enable {
description = "Burp Server";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
path = [ cfg.package pkgs.nettools pkgs.openssl ];
serviceConfig = {
ExecStart = "${cfg.package}/bin/burp -F -v -c ${serverConf}";
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
User = "burp";
Group = "burp";
UMask = "0077";
RuntimeDirectory = "burp";
PrivateTmp = true;
PrivateDevices = true;
ProtectSystem = true;
ProtectHome = true;
NoNewPrivileges = true;
ReadWriteDirectories = [
cfg.server.dataDirectory
serverHome
];
ExecStartPre = "+${pkgs.writeScript "burp-prestart" ''
#!/${pkgs.bash}/bin/bash
if ! [ -d "${cfg.server.dataDirectory}" ] ; then
mkdir -p "${cfg.server.dataDirectory}"
fi
if ! [ -d "${serverClientConfDir}" ] ; then
mkdir -p "${serverClientConfDir}"
fi
chown burp:burp "${cfg.server.dataDirectory}" "${serverClientConfDir}"
chmod 700 "${cfg.server.dataDirectory}" "${serverClientConfDir}"
${concatStringsSep "\n" (mapAttrsToList(name: file:
"ln -fs " + file + " " + serverClientConfDir + "/" + name) clientConfigs)}
''}";
Nice = 19;
IOSchedulingClass = "idle";
CPUSchedulingPolicy = "idle";
};
};
};
}

10
options/default.nix Normal file
View file

@ -0,0 +1,10 @@
{ ... }:
{
imports = [
./email.nix
./burp.nix
./auto-upgrade.nix
./status-email.nix
./btrfs-scrub.nix
];
}

28
options/email.nix Normal file
View file

@ -0,0 +1,28 @@
{ config, lib, ... }:
with lib;
{
options.email = {
fromAddress = mkOption {
type = types.str;
example = "noreply@example.com";
description = ''
E-Mail address automated e-mails are sent from.
'';
};
fromIdentity = mkOption {
type = types.str;
default = "${config.networking.hostName} <${config.email.fromAddress}>";
example = "Maintenance (no reply) <noreply@example.com>";
description = ''
E-Mail identity automated e-mails are sent from.
'';
};
adminEmail = mkOption {
type = types.str;
example = "admin@example.com";
description = ''
E-Mail address automated e-mails are sent to.
'';
};
};
}

95
options/status-email.nix Normal file
View file

@ -0,0 +1,95 @@
{ pkgs, lib, config, ... }:
let
fromAddress = config.email.fromAddress;
fromIdentity = config.email.fromIdentity;
toAddress = config.email.adminEmail;
cfg = config.services.statusEmail;
in
{
options.services.statusEmail = {
enable = lib.mkEnableOption "Send systemd status e-mails";
};
config.programs.msmtp = lib.mkIf cfg.enable {
enable = true;
setSendmail = true;
accounts = {
default = {
auth = true;
host = "gvfr.de";
passwordeval = "cat /secrets/email_password.txt";
user = fromAddress;
from = fromAddress;
port = 465;
tls = true;
tls_starttls = false;
};
};
};
config.systemd.services."status-email@" = let
sendStatusEmail = pkgs.writeScript "send-status-email" ''
#!${pkgs.bash}/bin/bash
from="${fromIdentity}"
to="${toAddress}"
service="$1"
full_status="$(systemctl status --full --lines 200 "$service")"
exit_code="$(echo "$full_status" | head -n5 | tail -1 | sed -e 's/.*status=\(.*\))$/\1/g')"
# state="$(systemctl is-failed "$service")"
fail_priority=1
fail_subject="Unit \"$service\" failure report (exit code $exit_code)"
success_priority=3
success_subject="Unit \"$service\" report (success)"
shift
while [ $# -gt 0 ] ; do
case "$1" in
'-s'|'--fail-subject')
fail_subject="$2"
shift 2
;;
'-p'|'--fail-priority')
fail_priority="$2"
shift 2
;;
*)
break
;;
esac
done
if [ "$exit_code" != "0/SUCCESS" ] ; then
subject="$fail_subject"
priority="$fail_priority"
else
subject="$success_subject"
priority="$success_priority"
fi
echo "Sending e-mail \"$subject\" to \"$to\"."
${pkgs.system-sendmail}/bin/sendmail -t -X - <<ERRMAIL
To: $to
From: $from
Subject: $subject
Content-Transfer-Encoding: 8bit
Content-Type: text/plain; charset=UTF-8
X-Priority: $priority
$full_status
ERRMAIL
'';
in
lib.mkIf cfg.enable {
unitConfig = {
Description = "Send a status e-mail for %I";
};
serviceConfig = {
Type = "oneshot";
ExecStart = "${sendStatusEmail} %i";
};
};
}

19
overlays/burp.nix Normal file
View file

@ -0,0 +1,19 @@
{ config, ... }:
{
nixpkgs.overlays = [
(self: super: {
burp = (super.burp.overrideAttrs (old: {
version = "3.1.4";
src = super.fetchFromGitHub {
owner = "grke";
repo = "burp";
rev = "3.1.4";
hash = "sha256-/vYon0XUIuMAaaaRNehzMspKMHWp0tJm8JubRt1KmZU=";
};
configureFlags = [ "--sysconfdir=/var/lib/burp" ];
buildInputs = with super; [ librsync ncurses openssl_1_1 zlib uthash ];
patches = [];
}));
})
];
}

6
overlays/default.nix Normal file
View file

@ -0,0 +1,6 @@
{ ... }:
{
imports = [
./burp.nix
];
}

48
packages/transcode.nix Normal file
View file

@ -0,0 +1,48 @@
{ stdenv
, lib
, fetchgit
, resholve
, bash
, file
, vorbis-tools
, flac
, ffmpeg
, unixtools
, exiftool
, makeWrapper
}:
stdenv.mkDerivation rec {
pname = "transcode";
version = "0.3.0";
src = fetchgit {
url = "https://git.25120.org/fruchti/transcode.git";
hash = "sha256-BN40KEQUQBRlwxMGWoPf8kya1kk2KveoU4ReASaKzZc=";
};
dontConfigure = true;
dontBuild = true;
buildInputs = [ bash file vorbis-tools flac ffmpeg unixtools.xxd exiftool ];
nativeBuildInputs = [ makeWrapper ];
installPhase = ''
install -D transcode $out/bin/transcode
wrapProgram $out/bin/transcode \
--prefix PATH : ${lib.makeBinPath [ bash file vorbis-tools flac ffmpeg unixtools.xxd exiftool ]}
'';
# solutions = {
# default = {
# scripts = [ "bin/transcode" ];
# interpreter = "${bash}/bin/bash";
# inputs = [ vorbis-tools flac ffmpeg unixtools.xxd exiftool ];
# };
# };
meta = with lib; {
homepage = "https://git.25120.org/fruchti/transcode";
description = "Transcode music from FLAC to MP3 and OGG";
platforms = platforms.linux;
};
}