commit c6245f319188fe2dc5351b9ac499d80ea97fff53
Author: breadcat <breadcat@users.noreply.github.com>
Date: Mon, 14 Jul 2025 15:33:23 +0100
Initial commit
Diffstat:
48 files changed, 1643 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1 @@
+research
+\ No newline at end of file
diff --git a/common/audio.nix b/common/audio.nix
@@ -0,0 +1,9 @@
+{
+ security.rtkit.enable = true;
+ services.pipewire = {
+ enable = true;
+ wireplumber.enable = true;
+ alsa.enable = true;
+ pulse.enable = true;
+ };
+}
diff --git a/common/emulators.nix b/common/emulators.nix
@@ -0,0 +1,26 @@
+{ pkgs, ... }:
+{
+ environment.systemPackages = with pkgs; [
+ # Emulators
+ blastem
+ cemu
+ dolphin-emu
+ duckstation
+ flycast
+ gopher64
+ mgba
+ pcsx2
+ ppsspp
+ punes
+ rpcs3
+ sameboy
+ snes9x
+ xemu
+ # Game engines
+ corsix-th
+ eduke32
+ openra
+ openrct2
+ openttd
+ ];
+}
diff --git a/common/flakes.nix b/common/flakes.nix
@@ -0,0 +1,3 @@
+{
+ nix.settings.experimental-features = [ "nix-command" "flakes" ];
+}
diff --git a/common/fonts.nix b/common/fonts.nix
@@ -0,0 +1,7 @@
+{ pkgs, ... }:
+
+{
+fonts.packages = with pkgs; [
+ nerd-fonts.jetbrains-mono
+];
+}
diff --git a/common/garbage.nix b/common/garbage.nix
@@ -0,0 +1,7 @@
+{
+ nix.gc = {
+ automatic = true;
+ dates = "weekly";
+ options = "--delete-older-than 30d";
+ };
+}
+\ No newline at end of file
diff --git a/common/locale.nix b/common/locale.nix
@@ -0,0 +1,17 @@
+{
+ time.timeZone = "Europe/London";
+ i18n.defaultLocale = "en_GB.UTF-8";
+ i18n.extraLocaleSettings = {
+ LC_ADDRESS = "en_GB.UTF-8";
+ LC_IDENTIFICATION = "en_GB.UTF-8";
+ LC_MEASUREMENT = "en_GB.UTF-8";
+ LC_MONETARY = "en_GB.UTF-8";
+ LC_NAME = "en_GB.UTF-8";
+ LC_NUMERIC = "en_GB.UTF-8";
+ LC_PAPER = "en_GB.UTF-8";
+ LC_TELEPHONE = "en_GB.UTF-8";
+ LC_TIME = "en_GB.UTF-8";
+ };
+ services.xserver.xkb.layout = "gb";
+ console.keyMap = "uk";
+}
+\ No newline at end of file
diff --git a/common/media-sort.nix b/common/media-sort.nix
@@ -0,0 +1,24 @@
+{ pkgs }:
+
+pkgs.buildGoModule {
+ pname = "media-sort";
+ version = "2.6.2";
+
+ src = pkgs.fetchFromGitHub {
+ owner = "jpillora";
+ repo = "media-sort";
+ rev = "v2.6.2";
+ sha256 = "078bqjlh9n0575apgv4aw6d92bm17riqc5yb64vxg2d9yf9mi4s4";
+ # nix-prefetch-url --unpack https://github.com/jpillora/media-sort/archive/refs/tags/v2.6.2.tar.gz
+ };
+
+ vendorHash = "sha256-JHnKBr2sxwAXjdrmpkENz4Sm76MmPgNlSVtA8WoXwmA";
+ # nix-prefetch-url https://github.com/jpillora/media-sort/archive/refs/tags/v2.6.2.tar.gz | xargs nix hash convert --to sri --hash-algo sha256
+
+ meta = with pkgs.lib; {
+ description = "Media sorter – organizes movies and TV into directories";
+ homepage = "https://github.com/jpillora/media-sort";
+ license = licenses.mit;
+ platforms = platforms.linux;
+ };
+}
diff --git a/common/nfs.nix b/common/nfs.nix
@@ -0,0 +1,7 @@
+{
+ fileSystems."/mnt" = {
+ device = "192.168.1.3:/mnt";
+ fsType = "nfs";
+ options = [ "x-systemd.automount" "noauto" ];
+ };
+}
+\ No newline at end of file
diff --git a/common/packages.nix b/common/packages.nix
@@ -0,0 +1,18 @@
+{ pkgs, ... }: {
+ nixpkgs.config.allowUnfree = true;
+ environment.systemPackages = with pkgs; [
+ fastfetch
+ ffmpeg
+ file
+ fish
+ git
+ htop
+ jdupes
+ neovim
+ rclone
+ rsync
+ syncthing
+ tmux
+ unzip
+ ];
+}
diff --git a/common/ssh-tunnel.nix b/common/ssh-tunnel.nix
@@ -0,0 +1,19 @@
+{ pkgs, username, domain, ... }:
+
+{
+ systemd.services.reverse-ssh-tunnel = {
+ description = "Persistent Reverse SSH Tunnel";
+ after = [ "network-online.target" ];
+ wants = [ "network-online.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ ExecStart = "${pkgs.openssh}/bin/ssh -NTg -o ServerAliveInterval=30 -o ExitOnForwardFailure=yes -o StrictHostKeyChecking=accept-new -p 55012 -i /home/${username}/vault/docs/secure/ssh-key-2022-02-16.key -R 55013:localhost:22 ${username}@${domain}";
+ Restart = "always";
+ RestartSec = "10s";
+ User = "${username}";
+ };
+ };
+
+ environment.systemPackages = with pkgs; [ openssh ];
+}
diff --git a/common/ssh.nix b/common/ssh.nix
@@ -0,0 +1,11 @@
+{ username, sshkey, ... }:
+
+{
+ services.openssh = {
+ enable = true;
+ settings.PasswordAuthentication = false;
+ };
+
+ # SSH key
+ users.users.${username}.openssh.authorizedKeys.keys = [ "${sshkey}" ];
+}
+\ No newline at end of file
diff --git a/common/syncthing.nix b/common/syncthing.nix
@@ -0,0 +1,40 @@
+{ username, ... }:
+{
+
+ services.syncthing = {
+ enable = true;
+ user = "${username}";
+ dataDir = "/home/${username}/";
+ configDir = "/home/${username}/.config/syncthing";
+ settings = {
+ options.urAccepted = 1;
+ devices = {
+ desktop.id = "6DL2MHG-4WS4B2Q-IAOHURV-XL3CXVZ-EBDXZMH-FZS7WFX-UJAVUJL-UQ2EOAQ";
+ htpc.id = "E46LP6X-6LMHIBU-LPQTF2P-T5VIU52-OJWUAP5-ZX7VCQU-S7GGGK3-Y4IXVAJ";
+ laptop.id = "L2DBXFX-T5B52M7-54AOF4S-HVGQGHM-XMEDPFI-NXX4PEI-V6YHD7P-JYGR2A3";
+ nas.id = "FONKXV6-BQFMLNT-6OHTKXG-CP7DOZP-M5ZA6GW-5WAN4L6-X3LEANG-7EC5WQ6";
+ phone.id = "7M34AP7-VLSE6A4-UX24I72-VDXCBSW-BGXHSUF-OF6UQQL-7QK4IFW-5F5M3QH";
+ server.id = "TJV7YEI-GYLINDA-6YYHJW7-TLV6XUY-LJEJWSV-AEZ6NKE-BFLX4KB-BJ5DNAH";
+ };
+ folders = {
+ "/home/${username}/vault" = {
+ label = "vault";
+ id = "vault";
+ devices = [ "desktop" "htpc" "laptop" "nas" "phone" "server" ];
+ };
+ };
+ };
+ };
+
+ # https://github.com/quic-go/quic-go/wiki/UDP-Buffer-Sizes
+ boot.kernel.sysctl."net.core.rmem_max" = 7500000;
+ boot.kernel.sysctl."net.core.wmem_max" = 7500000;
+
+ # Disable default ~/Sync folder
+ systemd.services.syncthing.environment.STNODEFAULTFOLDER = "true";
+
+ # Firewall ports
+ networking.firewall.allowedTCPPorts = [ 22000 ];
+ networking.firewall.allowedUDPPorts = [ 22000 21027 ];
+
+}
diff --git a/common/user.nix b/common/user.nix
@@ -0,0 +1,15 @@
+{ pkgs, username, fullname, ... }:
+{
+ users.users."${username}" = {
+ isNormalUser = true;
+ description = "${fullname}";
+ shell = pkgs.fish;
+ extraGroups = [ "networkmanager" "wheel" "video" ];
+ };
+
+ # Auto login
+ services.getty.autologinUser = "${username}";
+
+ # Enable fish shell
+ programs.fish.enable = true;
+}
diff --git a/common/ydotool.nix b/common/ydotool.nix
@@ -0,0 +1,27 @@
+{ pkgs, username, ... }:
+
+{
+ boot.kernelModules = [ "uinput" ];
+ users.users.${username}.extraGroups = [ "uinput" ];
+ # Define the uinput group
+ users.groups.uinput = {};
+
+ # Install ydotool system-wide
+ environment.systemPackages = with pkgs; [
+ ydotool
+ ];
+
+ # Udev rule to allow group access to /dev/uinput
+ services.udev.extraRules = ''
+ KERNEL=="uinput", GROUP="uinput", MODE="0660"
+ '';
+
+ # Set capabilities on ydotool via a wrapper so it persists rebuilds
+ security.wrappers.ydotool = {
+ source = "${pkgs.ydotool}/bin/ydotool";
+ capabilities = "cap_sys_admin,cap_dac_override,cap_sys_nice+ep";
+ owner = "root";
+ group = "uinput";
+ permissions = "u+rx,g+rx";
+ };
+}
diff --git a/entrypoint.nix b/entrypoint.nix
@@ -0,0 +1,24 @@
+{
+ config,
+ pkgs,
+ lib,
+ ...
+}: let
+ fullname = "Peter";
+ username = lib.strings.toLower fullname;
+ domain = "minskio.co.uk";
+ email = "${username}@${domain}";
+ sshkey = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCXdHG4d/CoCbS1mp7cg+/3qS8nI4bvp7nvU5BZdkzseOt1NerZ4rgdQLBiFGiEi4LPMOQxBGXe7uuskn3TCc2C/DkZH/+AdYQ5MDXRbRqta/0oS8XVTzWcBtluaHc6qsuF6MkSU853ZWVgzlYimfSkjkwvrMT38WkkauC9U4VoqODVLQe5sivR/2INHctNfj0dYuyvPRUhAiuTrha0cKrS7xkOIf4a9gQgunU4+cmyb1HPt6KmNMzuZ/nhsqVWf6h/v0oBTg8p+aestfpg2fTAlY8Za8t/ZOqpF1TeWqUB+1AXEoQHNw2bezzKwCyX39cvjTeE5EWKl7oXalq91J39 ssh-key-2022-02-16";
+ hostname =
+ if builtins.pathExists "/etc/hostname"
+ then lib.strings.removeSuffix "\n" (builtins.readFile "/etc/hostname")
+ else throw "Error: /etc/hostname not found. Please ensure the hostname is set before rebuild.";
+ machine = lib.strings.removeSuffix "\n" hostname;
+ osConfigPath = ./machines + "/${machine}.nix";
+in {
+ imports = [
+ (import osConfigPath {inherit config pkgs lib fullname machine username domain email sshkey;})
+ ];
+
+ networking.hostName = machine;
+}
diff --git a/home/alacritty.nix b/home/alacritty.nix
@@ -0,0 +1,42 @@
+{
+ programs.alacritty = {
+ enable = true;
+ settings = {
+ font = {
+ size = 11.0;
+ # draw_bold_text_with_bright_colors = true;
+ normal = {
+ family = "JetBrainsMono Nerd Font Mono";
+ style = "Regular";
+ };
+ };
+ colors = {
+ primary = {
+ background = "#1d1f21";
+ foreground = "#c5c8c6";
+ };
+ normal = {
+ black = "#282a2e";
+ red = "#a54242";
+ green = "#8c9440";
+ yellow = "#de935f";
+ blue = "#5f819d";
+ magenta = "#85678f";
+ cyan = "#5e8d87";
+ white = "#707880";
+ };
+ bright = {
+ black = "#373b41";
+ red = "#cc6666";
+ green = "#b5bd68";
+ yellow = "#f0c674";
+ blue = "#81a2be";
+ magenta = "#b294bb";
+ cyan = "#8abeb7";
+ white = "#c5c8c6";
+ };
+ };
+ };
+ };
+}
+
diff --git a/home/cursor.nix b/home/cursor.nix
@@ -0,0 +1,15 @@
+{ pkgs, lib, ... }:
+
+{
+home.pointerCursor = {
+ gtk.enable = true;
+ package = pkgs.posy-cursors;
+ name = "Posy_Cursor_Black";
+ size = 24;
+};
+
+nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [
+ "posy-cursors"
+ ];
+ }
+
diff --git a/home/firefox.nix b/home/firefox.nix
@@ -0,0 +1,30 @@
+{
+programs.firefox = {
+ enable = true;
+
+ profiles.default = {
+ id = 0;
+ name = "default";
+
+ # Example Firefox preferences
+ settings = {
+ "browser.aboutConfig.showWarning" = false;
+ "browser.gesture.swipe.left" = "";
+ "browser.gesture.swipe.right" = "";
+ "browser.startup.homepage" = "https://breadcat.github.io/startpage/";
+ "browser.theme.content-theme" = "0"; # Dark theme
+ "browser.theme.toolbar-theme" = "0"; # Dark theme
+ "layout.css.prefers-color-scheme.content-override" = "0"; # Dark CSS themes
+ "network.cookie.cookieBehavior" = 1; # Block third-party cookies
+ "privacy.donottrackheader.enabled" = true;
+ };
+ extensions = [
+ # "ublock-origin@raymondhill.net" # Just an example (must match extension ID)
+ # "uBlock0@raymondhill.net" # uBlock Origin
+ ];
+ };
+};
+home.sessionVariables = {
+ MOZ_ENABLE_WAYLAND = 1;
+};
+}
+\ No newline at end of file
diff --git a/home/fish.nix b/home/fish.nix
@@ -0,0 +1,29 @@
+{
+ programs.fish = {
+ enable = true;
+ functions = {
+ __fish_command_not_found_handler = { body = "echo fish: Unknown command $argv[1]"; onEvent = "fish_command_not_found"; };
+ vat = "math $argv + \"($argv * 0.2)\"";
+ mcd = "mkdir -p $argv[1] && cd $argv[1]";
+ mergeinto = "rsync --progress --remove-source-files -av \"$argv[1]\" \"$argv[2]\" && find \"$argv[1]\" -empty -delete";
+ };
+ loginShellInit = ''
+ set fish_greeting # Disable greeting
+ set --erase fish_greeting # Disable greeting
+ set -gx SYNCDIR $HOME/vault
+ set -gx EDITOR nvim
+ set -gx VISUAL $EDITOR
+ '';
+ shellAliases = {
+ extract = "aunpack";
+ jdupes = "jdupes -A"; # exclude hidden files
+ empties = "find . -maxdepth 3 -mount -not -path \"*/\.*\" -empty -print";
+ vaultedit = "find \"$SYNCDIR\" -maxdepth 5 -type f | fzf --preview \"cat {}\" --layout reverse | xargs -r -I{} \"$EDITOR\" {}";
+ week = "date +%V";
+ };
+# binds = {
+# "ctrl-h".command = "backward-kill-path-component";
+# "ctrl-backspace".command = "kill-word";
+# };
+ };
+}
diff --git a/home/ghostty.nix b/home/ghostty.nix
@@ -0,0 +1,37 @@
+{
+ programs.ghostty = {
+ enable = true;
+ settings = {
+ theme = "base16-dark";
+ font-size = 11;
+ };
+ themes =
+ {
+ base16-dark = {
+ background = "1d1f21";
+ cursor-color = "c5c8c6";
+ foreground = "c5c8c6";
+ palette = [
+ "0=#282a2e"
+ "1=#a54242"
+ "2=#8c9440"
+ "3=#de935f"
+ "4=#5f819d"
+ "5=#85678f"
+ "6=#5e8d87"
+ "7=#707880"
+ "8=#373b41"
+ "9=#cc6666"
+ "10=#b5bd68"
+ "11=#f0c674"
+ "12=#8ae2be"
+ "13=#b294bb"
+ "14=#8abeb7"
+ "15=#c5c8c6"
+ ];
+ selection-background = "353749";
+ selection-foreground = "cdd6f4";
+ };
+ };
+ };
+}
diff --git a/home/git.nix b/home/git.nix
@@ -0,0 +1,9 @@
+{ fullname, email, ... }:
+
+{
+ programs.git = {
+ enable = true;
+ userName = "${fullname}";
+ userEmail = "${email}";
+ };
+}
diff --git a/home/htop.nix b/home/htop.nix
@@ -0,0 +1,14 @@
+{
+ programs.htop = {
+ enable = true;
+ settings = {
+ shadow_other_users = 1;
+ tree_view = 1;
+ hide_userland_threads = 1;
+ columnMeters0 = ["AllCPUs" "Memory"];
+ columnMeterModes0 = [1 1];
+ columnMeters1 = ["DateTime" "Tasks" "LoadAverage" "Uptime" "System"];
+ columnMeterModes1 = [2 2 2 2 2];
+ };
+ };
+}
diff --git a/home/hyprland.nix b/home/hyprland.nix
@@ -0,0 +1,199 @@
+{ ... }:
+
+{
+ wayland.windowManager.hyprland = {
+ enable = true;
+ settings = {
+
+ ecosystem = {
+ "no_update_news" = true;
+ };
+
+ monitor = ",preferred,auto,auto";
+ env = [
+ "XCURSOR_SIZE,24"
+ "HYPRCURSOR_SIZE,24"
+ ];
+
+ general = {
+ gaps_in = 5;
+ gaps_out = 20;
+ border_size = 2;
+ "col.active_border" = "rgba(33ccffee) rgba(00ff99ee) 45deg";
+ "col.inactive_border" = "rgba(595959aa)";
+ resize_on_border = false;
+ allow_tearing = false;
+ layout = "dwindle";
+ };
+
+ cursor = {
+ inactive_timeout = 3;
+ };
+
+ decoration = {
+ rounding = 5;
+ active_opacity = 1.0;
+ inactive_opacity = 1.0;
+ shadow = {
+ enabled = true;
+ range = 4;
+ render_power = 3;
+ color = "rgba(1a1a1aee)";
+ };
+ blur = {
+ enabled = true;
+ size = 3;
+ passes = 1;
+ vibrancy = 0.1696;
+ };
+ };
+
+ animations = {
+ enabled = true;
+ bezier = [
+ "easeOutQuint,0.23,1,0.32,1"
+ "easeInOutCubic,0.65,0.05,0.36,1"
+ "linear,0,0,1,1"
+ "almostLinear,0.5,0.5,0.75,1.0"
+ "quick,0.15,0,0.1,1"
+ ];
+ animation = [
+ "global, 1, 10, default"
+ "border, 1, 5.39, easeOutQuint"
+ "windows, 1, 4.79, easeOutQuint"
+ "windowsIn, 1, 4.1, easeOutQuint, popin 87%"
+ "windowsOut, 1, 1.49, linear, popin 87%"
+ "fadeIn, 1, 1.73, almostLinear"
+ "fadeOut, 1, 1.46, almostLinear"
+ "fade, 1, 3.03, quick"
+ "layers, 1, 3.81, easeOutQuint"
+ "layersIn, 1, 4, easeOutQuint, fade"
+ "layersOut, 1, 1.5, linear, fade"
+ "fadeLayersIn, 1, 1.79, almostLinear"
+ "fadeLayersOut, 1, 1.39, almostLinear"
+ "workspaces, 1, 1.94, almostLinear, fade"
+ "workspacesIn, 1, 1.21, almostLinear, fade"
+ "workspacesOut, 1, 1.94, almostLinear, fade"
+ ];
+ };
+
+ dwindle = {
+ pseudotile = true;
+ preserve_split = true;
+ workspace = [
+ "w[tv1], gapsout:0, gapsin:0"
+ "f[1], gapsout:0, gapsin:0"
+ ];
+ windowrulev2 = [
+ "bordersize 0, floating:0, onworkspace:w[tv1]"
+ "rounding 0, floating:0, onworkspace:w[tv1]"
+ "bordersize 0, floating:0, onworkspace:f[1]"
+ "rounding 0, floating:0, onworkspace:f[1]"
+ ];
+ };
+
+ master = {
+ new_status = "master";
+ };
+
+ misc = {
+ force_default_wallpaper = 0;
+ disable_hyprland_logo = true;
+ };
+
+ input = {
+ kb_layout = "gb";
+ kb_options = "caps:backspace";
+ follow_mouse = 1;
+ sensitivity = 0;
+ touchpad = {
+ natural_scroll = false;
+ };
+ };
+
+ gestures = {
+ workspace_swipe = false;
+ };
+
+ bind = [
+ "SUPER, R, exec, tofi-drun | xargs hyprctl dispatch exec --"
+ "SUPER, W, exec, firefox"
+ "SUPER, T, exec, ghostty"
+ "SUPER_SHIFT, W, exec, firefox -private-window"
+ "ALT, Tab, cyclenext"
+ "ALT SHIFT, Tab, cyclenext, prev"
+ "SUPER, RETURN, exec, ghostty"
+ "SUPER_SHIFT, Q, killactive,"
+ "SUPER, X, exit,"
+ "SUPER, E, exec, ghostty -e lf"
+ "SUPER, Space, togglefloating,"
+ "SUPER, F, fullscreen,"
+ "SUPER, P, pseudo,"
+ "SUPER, J, togglesplit,"
+ "SUPER, left, movefocus, l"
+ "SUPER, right, movefocus, r"
+ "SUPER, up, movefocus, u"
+ "SUPER, down, movefocus, d"
+ "SUPER, 1, workspace, 1"
+ "SUPER, 2, workspace, 2"
+ "SUPER, 3, workspace, 3"
+ "SUPER, 4, workspace, 4"
+ "SUPER, 5, workspace, 5"
+ "SUPER, 6, workspace, 6"
+ "SUPER, 7, workspace, 7"
+ "SUPER, 8, workspace, 8"
+ "SUPER, 9, workspace, 9"
+ "SUPER, 0, workspace, 10"
+ "SUPER SHIFT, 1, movetoworkspace, 1"
+ "SUPER SHIFT, 2, movetoworkspace, 2"
+ "SUPER SHIFT, 3, movetoworkspace, 3"
+ "SUPER SHIFT, 4, movetoworkspace, 4"
+ "SUPER SHIFT, 5, movetoworkspace, 5"
+ "SUPER SHIFT, 6, movetoworkspace, 6"
+ "SUPER SHIFT, 7, movetoworkspace, 7"
+ "SUPER SHIFT, 8, movetoworkspace, 8"
+ "SUPER SHIFT, 9, movetoworkspace, 9"
+ "SUPER SHIFT, 0, movetoworkspace, 10"
+ "SUPER, mouse_down, workspace, e+1"
+ "SUPER, mouse_up, workspace, e-1"
+ ];
+
+ bindm = [
+ "ALT, mouse:272, movewindow"
+ "ALT, mouse:273, resizewindow"
+ ];
+
+ bindel = [
+ ",XF86AudioRaiseVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+"
+ ",XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-"
+ ",XF86AudioMute, exec, wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"
+ ",XF86AudioMicMute, exec, wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle"
+ ",XF86MonBrightnessUp, exec, brightnessctl s 10%+"
+ ",XF86MonBrightnessDown, exec, brightnessctl s 10%-"
+ ];
+
+ bindl = [
+ ", XF86AudioNext, exec, playerctl next"
+ ", XF86AudioPause, exec, playerctl play-pause"
+ ", XF86AudioPlay, exec, playerctl play-pause"
+ ", XF86AudioPrev, exec, playerctl previous"
+ ];
+
+ windowrulev2 = [
+ "suppressevent maximize, class:.*"
+ "nofocus,class:^$,title:^$,xwayland:1,floating:1,fullscreen:0,pinned:0"
+ ];
+ };
+ };
+
+ programs.fish = {
+ interactiveShellInit = ''
+ set -gx HYPRPATH (whereis Hyprland | awk '{print $NF}')
+ if test (tty) = "/dev/tty1"; exec $HYPRPATH; end
+ '';
+ };
+
+ dconf = {
+ enable = true;
+ };
+}
diff --git a/home/kodi.nix b/home/kodi.nix
@@ -0,0 +1,60 @@
+{ username, ... }:
+
+{
+ programs.kodi = {
+ enable = true;
+ settings = {
+ "addons.unknownsources" = "true";
+ "addons.updatemode" = "1";
+ "locale.country" = "UK (12h)";
+ "locale.language" = "resource.language.en_gb";
+ "locale.timezone" = "Europe/London";
+ "locale.timezonecountry" = "Britain (UK)";
+ "lookandfeel.skincolors" = "midnight";
+ "screensaver.mode" = "screensaver.xbmc.builtin.dim";
+ "screensaver.time" = "5";
+ "services.webserver" = "true";
+ "services.webserverauthentication" = "true";
+ "services.webserverpassword" = "kodi";
+ "services.webserverport" = "8080";
+ "services.webserverusername" = "kodi";
+ "videolibrary.tvshowsselectfirstunwatcheditem" = "2";
+ };
+ addonSettings = {
+ "service.watchedlist" = {
+ "dbpath" = "/home/${username}/vault/";
+ "dbfilename" = "watchedlist.db";
+ };
+ "skin.estuary" = {
+ "homemenunopicturesbutton" = "true";
+ "homemenunoradiobutton" = "true";
+ "homemenunofavbutton" = "true";
+ "homemenunomusicbutton" = "true";
+ "homemenunomusicvideobutton" = "true";
+ "homemenunovideosbutton" = "true";
+ "homemenunotvbutton" = "true";
+ };
+ };
+ sources = {
+ video = {
+ default = "movies";
+ source = [
+ { name = "television"; path = "/mnt/media/videos/television"; allowsharing = "true"; }
+ { name = "movies"; path = "/mnt/media/videos/movies"; allowsharing = "true"; }
+ { name = "${username}"; path = "/home/${username}"; allowsharing = "true"; }
+ ];
+ };
+ files = {
+ source = [
+ { name = "a4kSubtitles-repo"; path = "https://a4k-openproject.github.io/a4kSubtitles/packages/"; allowsharing = "true"; }
+ ];
+ };
+ };
+ };
+ # Launch Kodi (and fix fullscreen issue) via Hyprland
+ wayland.windowManager.hyprland = {
+ settings = {
+ "exec-once" = "htpc-launcher";
+ };
+ };
+}
diff --git a/home/lf.nix b/home/lf.nix
@@ -0,0 +1,17 @@
+{
+ programs.lf = {
+ enable = true;
+ settings = {
+ icons = true;
+ ignorecase = true;
+ };
+ keybindings = {
+ "." = "set hidden!";
+ "<delete>" = "delete";
+ "<enter>" = "shell";
+ "d" = "delete";
+ "i" = "$swayimg -r *";
+ "gv" = "cd ~/vault";
+ };
+ };
+}
diff --git a/home/mpv.nix b/home/mpv.nix
@@ -0,0 +1,41 @@
+{ pkgs, ... }:
+
+{
+ programs.mpv = {
+ enable = true;
+ config = {
+ volume = 50; # initial volume
+ audio-display = "no";
+ sub-auto = "fuzzy";
+ ytdl-raw-options = "sub-format=en,write-srt=";
+ ytdl-format = "bestvideo[height<=?480][fps<=?30]+bestaudio/best";
+ };
+ scripts = with pkgs.mpvScripts; [
+ sponsorblock-minimal
+ ];
+
+ profiles = {
+ "extension.gif" = { loop-file = "inf"; };
+ "extension.webm" = { loop-file = "inf"; };
+ "extension.jpg" = { pause = "yes"; };
+ "extension.jpeg" = { pause = "yes"; };
+ "extension.webp" = { pause = "yes"; };
+ "extension.png" = { pause = "yes"; };
+ "extension.avif" = { pause = "yes"; };
+ };
+ bindings = {
+ "-" = "add volume -5";
+ "=" = "add volume 5";
+ PGDWN = "playlist-prev";
+ PGUP = "playlist-next";
+ x = "cycle sub";
+ X = "cycle sub-visibility";
+ "Ctrl+n" = "af toggle acompressor";
+ "Alt+-" = "add video-zoom -0.02";
+ "Alt+=" = "add video-zoom 0.02";
+ RIGHT = "osd-msg-bar seek +5 exact";
+ LEFT = "osd-msg-bar seek -5 exact";
+ "ctrl+del" = "run rm '$\{path\}'";
+ };
+ };
+}
diff --git a/home/neovim.nix b/home/neovim.nix
@@ -0,0 +1,41 @@
+{
+ ...
+}: {
+ programs.neovim = {
+ enable = true;
+ defaultEditor = true;
+ extraConfig = ''
+ set smartcase
+ set nocompatible
+ let mapleader =","
+ set backspace=indent,eol,start
+ set clipboard+=unnamedplus
+ set ignorecase
+ set linebreak
+ set mouse=a
+ set nohlsearch
+ set number
+ set title
+ set titlestring=%f\ %m
+ set whichwrap+=<,>,h,l,[,]
+ set list listchars=nbsp:¬,tab:»·,trail:·,extends:>
+ set shiftwidth=2
+ set softtabstop=2
+ set tabstop=2
+ syntax on
+ map <C-H> <C-W>
+ map <C-Del> X<Esc>ce<del>
+ map <F8> :setlocal spell! spelllang=en_gb<CR>
+ xnoremap <A-z> :s/^.<CR>gv " alt-z removes first character of line
+ nnoremap <A-z> 0x " alt-z in normal mode removes first char
+ :iab <expr> _date strftime("%F")
+ :iab <expr> _time strftime("%H:%M")
+ :iab <expr> _stamp strftime("%F\T%H:%M:00")
+ autocmd BufWritePre * %s/\s\+$//e
+ autocmd BufWritepre * %s/\n\+\%$//e
+ autocmd FileType nix setlocal tabstop=2 shiftwidth=2 expandtab
+ autocmd BufWritePre *.nix %s/\s\+$//e | retab
+ autocmd BufWritePost *.nix silent! execute '!alejandra -qq %' | edit
+ '';
+ };
+}
diff --git a/home/newsboat.nix b/home/newsboat.nix
@@ -0,0 +1,25 @@
+{ domain, username, ... }:
+
+{
+ programs.newsboat = {
+ enable = true;
+ extraConfig = ''
+ show-read-feeds no
+ auto-reload yes
+ reload-time 45
+ browser $BROWSER
+ bind-key RIGHT open
+ bind-key LEFT quit
+ bind-key a toggle-article-read
+ bind-key m toggle-show-read-feeds
+ bind-key n next-unread
+ bind-key N prev-unread
+ macro m set browser "mpv %u" ; open-in-browser-and-mark-read ; set browser "$BROWSER %u"
+ urls-source "freshrss"
+ freshrss-url "https://rss.${domain}/api/greader.php"
+ freshrss-login "${username}"
+ freshrss-passwordeval "rbw get 'freshrss api'"
+ '';
+ };
+}
+
diff --git a/home/rbw.nix b/home/rbw.nix
@@ -0,0 +1,13 @@
+{ pkgs, domain, email, ... }:
+
+{
+ programs.rbw = {
+ enable = true;
+ settings = {
+ base_url = "https://pass.${domain}";
+ email = "${email}";
+ pinentry = pkgs.pinentry-tty;
+ };
+ };
+}
+
diff --git a/home/ssh.nix b/home/ssh.nix
@@ -0,0 +1,52 @@
+{ domain, username, ... }:
+
+{
+ programs.ssh = {
+ enable = true;
+
+ matchBlocks = {
+ "minskio" = {
+ hostname = "${domain}";
+ user = "${username}";
+ port = 55012;
+ identityFile = "~/vault/docs/secure/ssh-key-2022-02-16.key";
+ };
+ "tunnel" = {
+ hostname = "${domain}";
+ user = "${username}";
+ port = 55012;
+ identityFile = "~/vault/docs/secure/ssh-key-2022-02-16.key";
+ extraOptions = {
+ RemoteCommand = "ssh -p 55013 ${username}@localhost -i ~/vault/docs/secure/ssh-key-2022-02-16.key";
+ RequestTTY = "force";
+ };
+ };
+ "htpc" = {
+ hostname = "192.168.1.6";
+ user = "${username}";
+ port = 22;
+ identityFile = "~/vault/docs/secure/ssh-key-2022-02-16.key";
+ };
+ "nas" = {
+ hostname = "192.168.1.3";
+ user = "${username}";
+ port = 22;
+ identityFile = "~/vault/docs/secure/ssh-key-2022-02-16.key";
+ };
+ "router" = {
+ hostname = "192.168.1.1";
+ user = "root";
+ port = 22;
+ };
+ "ap" = {
+ hostname = "192.168.1.2";
+ user = "root";
+ port = 22;
+ extraOptions = {
+ HostKeyAlgorithms = "+ssh-rsa";
+ };
+ };
+ };
+ };
+
+}
diff --git a/home/tofi.nix b/home/tofi.nix
@@ -0,0 +1,17 @@
+{
+ programs.tofi = {
+ enable = true;
+ settings = {
+ width = "100%";
+ height = "100%";
+ border-width = "0";
+ outline-width = "0";
+ padding-left = "35%";
+ padding-top = "35%";
+ result-spacing = "25";
+ num-results = "10";
+ font = "monospace";
+ background-color = "#000A";
+ };
+ };
+}
diff --git a/machines/arcadia-hardware.nix b/machines/arcadia-hardware.nix
@@ -0,0 +1,37 @@
+{ config, lib, modulesPath, ... }:
+
+{
+ imports =
+ [ (modulesPath + "/installer/scan/not-detected.nix")
+ ];
+
+ boot.initrd.availableKernelModules = [ "xhci_pci" "ehci_pci" "ahci" "usbhid" "usb_storage" "sd_mod" ];
+ boot.initrd.kernelModules = [ ];
+ boot.kernelModules = [ "kvm-intel" ];
+ boot.extraModulePackages = [ ];
+
+ fileSystems."/" =
+ { device = "/dev/disk/by-uuid/705ee72a-0cbf-40a3-a6cb-4ddd976b3d8f";
+ fsType = "ext4";
+ };
+
+ fileSystems."/boot" =
+ { device = "/dev/disk/by-uuid/7D86-A86F";
+ fsType = "vfat";
+ options = [ "fmask=0077" "dmask=0077" ];
+ };
+
+ swapDevices = [ ];
+
+ nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
+ hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
+
+ # Bootloader
+ boot.loader.systemd-boot.enable = true;
+ boot.loader.efi.canTouchEfiVariables = true;
+
+ # Networking, use DHCP
+ networking.networkmanager.enable = true;
+ networking.useDHCP = lib.mkDefault true;
+
+}
diff --git a/machines/arcadia.nix b/machines/arcadia.nix
@@ -0,0 +1,83 @@
+# HTPC
+
+{ config, pkgs, domain, machine, username, fullname, sshkey, ... }:
+
+let
+ home-manager = builtins.fetchTarball https://github.com/nix-community/home-manager/archive/release-25.05.tar.gz; # stable
+ # home-manager = builtins.fetchTarball https://github.com/nix-community/home-manager/archive/master.tar.gz; # unstable
+in
+
+{
+ # Common OS imports
+ imports =
+ [ # Include the results of the hardware scan.
+ ./${machine}-hardware.nix
+ ../common/audio.nix
+ ../common/flakes.nix
+ ../common/garbage.nix
+ ../common/locale.nix
+ ../common/nfs.nix
+ # ../common/kodi-module.nix
+ ../common/packages.nix
+ (import ../common/syncthing.nix {inherit config pkgs username;})
+ (import ../common/user.nix {inherit config pkgs username fullname;})
+ (import ../common/ssh.nix {inherit username sshkey;})
+ ../scripts/htpc-launcher.nix
+ (import "${home-manager}/nixos")
+ ];
+
+ # Home-Manager
+ home-manager.backupFileExtension = "hm-bak";
+ home-manager.users.${username} = { pkgs, ... }: {
+ imports = [
+ ../home/fish.nix
+ ../home/hyprland.nix
+ (import ../home/kodi.nix {inherit username;})
+ (import ../home/ssh.nix {inherit domain username;})
+ ];
+
+ # The state version is required and should stay at the version you
+ # originally installed.
+ home.stateVersion = "24.11";
+ };
+
+ # Hostname
+ networking.hostName = "arcadia"; # Define your hostname.
+
+ # Hardware acceleration
+ hardware.graphics = {
+ enable = true;
+ extraPackages = with pkgs; [
+ intel-media-driver
+ libvdpau-va-gl
+ vaapiIntel
+ ];
+ };
+
+ # Enable programs
+ programs.hyprland.enable = true;
+
+ # Packages
+ environment.systemPackages = with pkgs; [
+ alacritty
+ # duckstation
+ hyprland
+ kodiPackages.inputstream-adaptive
+ kodi-wayland
+ moonlight-qt
+ mpv
+ # spotify
+ yt-dlp
+ ];
+
+ # Kodi settings
+ # HDMI CEC input groups
+ users.users.${username}.extraGroups = [ "networkmanager" "wheel" "input" "dialout" "video" ]; # Extra groups for Kodi CEC input
+
+ # Web UI firewall rules
+ networking.firewall.allowedTCPPorts = [ 8080 ];
+ networking.firewall.allowedUDPPorts = [ 8080 ];
+
+ system.stateVersion = "24.11";
+
+}
diff --git a/machines/ilias-hardware.nix b/machines/ilias-hardware.nix
@@ -0,0 +1,39 @@
+{ config, lib, modulesPath, ... }:
+
+{
+ imports =
+ [ (modulesPath + "/installer/scan/not-detected.nix")
+ ];
+
+ boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "usbhid" "usb_storage" "sd_mod" "sr_mod" ];
+ boot.initrd.kernelModules = [ ];
+ boot.kernelModules = [ "kvm-intel" ];
+ boot.extraModulePackages = [ ];
+
+ fileSystems."/" =
+ { device = "/dev/disk/by-uuid/8018d8fb-dffe-40a2-b5f9-500000b24c36";
+ fsType = "ext4";
+ };
+
+ fileSystems."/boot" =
+ { device = "/dev/disk/by-uuid/EE4D-E953";
+ fsType = "vfat";
+ options = [ "fmask=0077" "dmask=0077" ];
+ };
+
+ swapDevices =
+ [ { device = "/dev/disk/by-uuid/3397e636-91db-44ae-9572-923e4b3acbbe"; }
+ ];
+
+ nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
+ hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
+
+ # Bootloader
+ boot.loader.systemd-boot.enable = true;
+ boot.loader.efi.canTouchEfiVariables = true;
+
+ # Networking, use DHCP
+ networking.networkmanager.enable = true;
+ networking.useDHCP = lib.mkDefault true;
+
+}
diff --git a/machines/ilias.nix b/machines/ilias.nix
@@ -0,0 +1,109 @@
+# NAS
+{
+ config,
+ pkgs,
+ machine,
+ username,
+ email,
+ fullname,
+ domain,
+ sshkey,
+ ...
+}: let
+ media-sort = import ../common/media-sort.nix {inherit pkgs;};
+ # home-manager = builtins.fetchTarball https://github.com/nix-community/home-manager/archive/release-25.05.tar.gz; # Stable
+ home-manager = builtins.fetchTarball https://github.com/nix-community/home-manager/archive/master.tar.gz; # Unstable
+in {
+ # Core OS imports
+ imports = [
+ # Include the results of the hardware scan.
+ ./${machine}-hardware.nix
+ ../common/flakes.nix
+ ../common/locale.nix
+ ../common/packages.nix
+ ../scripts/phone-dump.nix
+ ../scripts/watchedlist.nix
+ ../scripts/ctimerename.nix
+ ../scripts/duupmove.nix
+ ../scripts/youtube-id-rss.nix
+ (import ../scripts/overtid.nix {inherit pkgs;})
+ (import ../scripts/blog-sort-archives.nix {inherit pkgs domain;})
+ (import ../scripts/blog-sort-quotes.nix {inherit pkgs domain;})
+ (import ../scripts/blog-sort-languages.nix {inherit pkgs domain;})
+ (import ../common/ssh-tunnel.nix {inherit config pkgs username domain;})
+ (import ../common/syncthing.nix {inherit config pkgs username;})
+ (import ../common/user.nix {inherit config pkgs username fullname;})
+ (import ../common/ssh.nix {inherit username sshkey;})
+ (import "${home-manager}/nixos")
+ ];
+
+ # Home-Manager
+ home-manager.backupFileExtension = "hm-bak";
+ home-manager.users.${username} = {pkgs, ...}: {
+ imports = [
+ ../home/fish.nix
+ ../home/htop.nix
+ ../home/neovim.nix
+ (import ../home/git.nix {inherit fullname email;})
+ (import ../home/rbw.nix {inherit pkgs domain email;})
+ (import ../home/ssh.nix {inherit domain username;})
+ ];
+ # The state version is required and should stay at the version you
+ # originally installed.
+ home.stateVersion = "24.11";
+ };
+
+ # Hostname
+ networking.hostName = "ilias"; # Define your hostname.
+
+ # Second drive and NFS
+ fileSystems."/mnt" = {
+ device = "/dev/disk/by-uuid/9b205675-7376-45ba-b575-2f36eb50ea99";
+ fsType = "ext4";
+ };
+ services.nfs.server = {
+ enable = true;
+ exports = ''
+ /mnt 192.168.1.0/24(rw)
+ '';
+ };
+ # Firewall and NFS server ports
+ networking.firewall.enable = true;
+ networking.firewall.allowPing = true;
+ networking.firewall.allowedTCPPorts = [111 2049];
+ networking.firewall.allowedUDPPorts = [111 2049];
+
+ # Packages
+ environment.systemPackages = with pkgs; [
+ czkawka
+ atool
+ dos2unix
+ fzf
+ gallery-dl
+ imagemagick
+ jdupes
+ media-sort
+ mmv
+ lf
+ mnamer
+ mp3val
+ ngrok
+ nixfmt-rfc-style
+ ocrmypdf
+ optipng
+ opustags
+ pciutils
+ powertop
+ python3
+ qpdf
+ rbw
+ rclone
+ shellcheck-minimal
+ shfmt
+ sqlite
+ unrar
+ yt-dlp
+ ];
+
+ system.stateVersion = "24.11";
+}
diff --git a/machines/minerva-hardware.nix b/machines/minerva-hardware.nix
@@ -0,0 +1,32 @@
+{ config, lib, modulesPath, ... }:
+
+{
+ imports =
+ [ (modulesPath + "/installer/scan/not-detected.nix")
+ ];
+
+ boot.initrd.availableKernelModules = [ "xhci_pci" "ehci_pci" "ahci" "usb_storage" "sd_mod" "sdhci_pci" ];
+ boot.initrd.kernelModules = [ ];
+ boot.kernelModules = [ "kvm-intel" ];
+ boot.extraModulePackages = [ ];
+
+ fileSystems."/" =
+ { device = "/dev/disk/by-uuid/e9802357-1ee2-4e24-bf94-580c96205012";
+ fsType = "ext4";
+ };
+
+ swapDevices = [ ];
+
+ nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
+ hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
+
+ # Bootloader
+ boot.loader.grub.enable = true;
+ boot.loader.grub.device = "/dev/sda";
+ boot.loader.grub.useOSProber = true;
+
+ # Networking, use DHCP
+ networking.networkmanager.enable = true;
+ networking.useDHCP = lib.mkDefault true;
+
+}
diff --git a/machines/minerva.nix b/machines/minerva.nix
@@ -0,0 +1,99 @@
+# Laptop
+{
+ config,
+ pkgs,
+ machine,
+ username,
+ fullname,
+ domain,
+ email,
+ sshkey,
+ ...
+}: let
+ media-sort = import ../common/media-sort.nix {inherit pkgs;};
+ # home-manager = builtins.fetchTarball https://github.com/nix-community/home-manager/archive/release-25.05.tar.gz;
+ home-manager = builtins.fetchTarball https://github.com/nix-community/home-manager/archive/master.tar.gz;
+in {
+ # Core OS imports
+ imports = [
+ # Include the results of the hardware scan.
+ ./${machine}-hardware.nix
+ ../common/audio.nix
+ ../common/flakes.nix
+ ../common/fonts.nix
+ ../common/garbage.nix
+ ../common/locale.nix
+ ../common/nfs.nix
+ ../common/packages.nix
+ (import ../common/syncthing.nix {inherit config pkgs username;})
+ (import ../common/user.nix {inherit config pkgs username fullname;})
+ (import ../common/ssh.nix {inherit username sshkey;})
+ (import ../common/ydotool.nix {inherit pkgs username;})
+ (import "${home-manager}/nixos")
+ ];
+
+ # Home-Manager
+ home-manager.backupFileExtension = "hm-bak";
+ home-manager.users.${username} = {pkgs, ...}: {
+ imports = [
+ ../home/fish.nix
+ ../home/alacritty.nix
+ ../home/ghostty.nix
+ ../home/cursor.nix
+ ../home/firefox.nix
+ ../home/fish.nix
+ ../home/htop.nix
+ # ../home/iamb.nix
+ ../home/hyprland.nix
+ ../home/lf.nix
+ ../home/mpv.nix
+ ../home/neovim.nix
+ ../home/tofi.nix
+ (import ../home/git.nix {inherit fullname email;})
+ (import ../home/rbw.nix {inherit pkgs domain email;})
+ (import ../home/ssh.nix {inherit domain username;})
+ (import ../home/newsboat.nix {inherit pkgs domain username;})
+ ];
+ # The state version is required and should stay at the version you
+ # originally installed.
+ home.stateVersion = "24.11";
+ };
+
+ # Hostname
+ networking.hostName = "minerva"; # Define your hostname.
+
+ # Packages
+ environment.systemPackages = with pkgs; [
+ atool
+ media-sort
+ brightnessctl
+ dos2unix
+ firefox
+ fzf
+ gallery-dl
+ glib
+ hyprcursor
+ hypridle
+ hyprland
+ imagemagick
+ jre8
+ lf
+ mpv
+ newsboat
+ pinentry-tty
+ posy-cursors
+ rbw
+ seatd
+ swayimg
+ tofi
+ unzip
+ wl-clipboard
+ yt-dlp
+ ];
+
+ programs.hyprland.enable = true;
+ users.users.${username}.extraGroups = ["seat" "video"];
+ services.seatd.enable = true;
+
+ system.stateVersion = "24.11"; # Did you read the comment?
+}
diff --git a/scripts/blog-sort-archives.nix b/scripts/blog-sort-archives.nix
@@ -0,0 +1,52 @@
+{
+ pkgs,
+ domain,
+ ...
+}: let
+ blog-sort-archives = pkgs.writeShellScriptBin "blog-sort-archives" ''
+ # variables
+ movies_export="$HOME/vault/src/blog.${domain}/content/posts/archived-movies.md"
+ tvshows_export="$HOME/vault/src/blog.${domain}/content/posts/archived-television.md"
+ # functions
+ function lastmod {
+ echo -n "Amending lastmod value... "
+ mod_timestamp="$(date +%FT%H:%M:00)"
+ sed -i "s/lastmod: .*/lastmod: $mod_timestamp/g" "$1"
+ echo -e "$i \e[32mdone\e[39m"
+ }
+ # process
+ movies_shasum_original="$(sha512sum "$movies_export" | awk '{print $1}')"
+ movie_header="$(grep -v "^*" "$movies_export")"
+ movies_raw="$(grep "^*" "$movies_export")"
+ echo -n "Writing movies export... "
+ {
+ printf "%s\n" "$movie_header"
+ printf "\n%s" "$movies_raw" | sort | uniq -i
+ } >"$movies_export"
+ movies_shasum_modified="$(sha512sum "$movies_export" | awk '{print $1}')"
+ if [[ "$movies_shasum_original" != "$movies_shasum_modified" ]]; then
+ lastmod "$movies_export" 1>/dev/null
+ echo -e "\e[32mmodified\e[39m"
+ else
+ echo -e "\e[33munmodified\e[39m"
+ fi
+ # tv shows
+ tvshows_shasum_original="$(sha512sum "$tvshows_export" | awk '{print $1}')"
+ tvshows_header="$(grep -v "^*" "$tvshows_export")"
+ tvshows_raw="$(grep "^*" "$tvshows_export")"
+ echo -n "Writing TV shows export... "
+ {
+ printf "%s\n" "$tvshows_header"
+ printf "\n%s" "$tvshows_raw" | sort | uniq -i
+ } >"$tvshows_export"
+ tvshows_shasum_modified="$(sha512sum "$tvshows_export" | awk '{print $1}')"
+ if [[ "$tvshows_shasum_original" != "$tvshows_shasum_modified" ]]; then
+ lastmod "$tvshows_export" 1>/dev/null
+ echo -e "\e[32mmodified\e[39m"
+ else
+ echo -e "\e[33munmodified\e[39m"
+ fi
+ '';
+in {
+ environment.systemPackages = [blog-sort-archives];
+}
diff --git a/scripts/blog-sort-languages.nix b/scripts/blog-sort-languages.nix
@@ -0,0 +1,35 @@
+{
+ pkgs,
+ domain,
+ ...
+}: let
+ blog-sort-languages = pkgs.writeShellScriptBin "blog-sort-languages" ''
+ # functions
+ function lastmod {
+ echo -n "Amending lastmod value... "
+ mod_timestamp="$(date +%FT%H:%M:00)"
+ sed -i "s/lastmod: .*/lastmod: $mod_timestamp/g" "$1"
+ echo -e "$i \e[32mdone\e[39m"
+ }
+ for i in $HOME/vault/src/blog.${domain}/content/languages/*; do
+ if [[ "$i" = *index.md ]]; then continue; fi # there's probably a better way of doing this, but I can't figure it out
+ echo -n "Processing $(basename "$i")... "
+ shasum_original="$(sha512sum "$i" | awk '{print $1}')"
+ file_header="$(head -n 8 "$i")"
+ file_body="$(tail -n +9 "$i" | sort | uniq -i)"
+ {
+ printf "%s\n" "$file_header"
+ printf "%s" "$file_body"
+ } >"$i"
+ shasum_modified="$(sha512sum "$i" | awk '{print $1}')"
+ if [[ "$shasum_original" != "$shasum_modified" ]]; then
+ lastmod "$i" 1>/dev/null
+ echo -e "\e[32mmodified\e[39m"
+ else
+ echo -e "\e[33munmodified\e[39m"
+ fi
+ done
+ '';
+in {
+ environment.systemPackages = [blog-sort-languages];
+}
diff --git a/scripts/blog-sort-quotes.nix b/scripts/blog-sort-quotes.nix
@@ -0,0 +1,34 @@
+{
+ pkgs,
+ domain,
+ ...
+}: let
+ blog-sort-quotes = pkgs.writeShellScriptBin "blog-sort-quotes" ''
+ # variables
+ quote_file="$HOME/vault/src/blog.${domain}/content/quotes.md"
+ file_header="$(head -n 7 "$quote_file")"
+ file_body="$(tail -n +7 "$quote_file" | sort | uniq -i | sed G)"
+ # functions
+ function lastmod {
+ echo -n "Amending lastmod value... "
+ mod_timestamp="$(date +%FT%H:%M:00)"
+ sed -i "s/lastmod: .*/lastmod: $mod_timestamp/g" "$1"
+ echo -e "$i \e[32mdone\e[39m"
+ }
+ echo -n "Processing $(basename "$quote_file")... "
+ shasum_original="$(sha512sum "$quote_file" | awk '{print $1}')"
+ {
+ printf "%s\n" "$file_header"
+ printf "%s" "$file_body"
+ } >"$quote_file"
+ shasum_modified="$(sha512sum "$quote_file" | awk '{print $1}')"
+ if [[ "$shasum_original" != "$shasum_modified" ]]; then
+ lastmod "$i" 1>/dev/null
+ echo -e "\e[32mmodified\e[39m"
+ else
+ echo -e "\e[33munmodified\e[39m"
+ fi
+ '';
+in {
+ environment.systemPackages = [blog-sort-quotes];
+}
diff --git a/scripts/ctimerename.nix b/scripts/ctimerename.nix
@@ -0,0 +1,27 @@
+{pkgs, ...}: let
+ ctimerename = pkgs.writeShellScriptBin "ctimerename" ''
+ extension="$1"
+ if [ -z "$extension" ]; then
+ echo "Extension variable is empty. Please specify a extension, e.g. jpg"
+ exit 1
+ fi
+ for file in *."$extension"; do
+ if [ -f "$file" ]; then
+ timestamp=$(stat -c %y "$file" | cut -d'.' -f1 | sed 's/[: ]/-/g')
+ newname="''${timestamp}.$extension"
+
+ # If the filename exists, add a counter
+ count=1
+ while [ -e "$newname" ]; do
+ newname="''${timestamp}_$count.$extension"
+ ((count++))
+ done
+
+ echo "Renaming '$file' to '$newname'"
+ mv "$file" "$newname"
+ fi
+ done
+ '';
+in {
+ environment.systemPackages = [ctimerename];
+}
diff --git a/scripts/duupmove.nix b/scripts/duupmove.nix
@@ -0,0 +1,34 @@
+{pkgs, ...}: let
+ duupmove = pkgs.writeShellScriptBin "duupmove" ''
+
+ target_dir="$1"
+
+ if [ -z "$target_dir" ]; then
+ echo "Target directory variable is empty. Please specify a directory, e.g. /mnt/destination/"
+ exit 1
+ fi
+
+ mkdir -p "$target_dir"
+
+ # Pictures
+ for file in *.jpg; do
+ if [[ "$file" =~ ^[0-9]+_[0-9a-f]{32}\.jpg$ ]]; then
+ mv "$file" "$target_dir/" -vin
+ fi
+ if [[ "$file" =~ ^[0-9]{9}_[A-Za-z0-9]{10}\.jpg$ ]]; then
+ mv "$file" "$target_dir/" -vin
+ fi
+ done
+
+ # Videos
+ for file in *.mp4; do
+ if [[ "$file" =~ ^[0-9a-f]{8}-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.mp4$ ]]; then
+ mv "$file" "$target_dir/" -vin
+ fi
+ done
+
+ jdupes "$target_dir" "." -d
+ '';
+in {
+ environment.systemPackages = [duupmove];
+}
diff --git a/scripts/htpc-launcher.nix b/scripts/htpc-launcher.nix
@@ -0,0 +1,11 @@
+{pkgs, ...}: let
+ htpc-launcher = pkgs.writeShellScriptBin "htpc-launcher" ''
+ kodi &
+ sleep 4
+ kodi-send -a toggleFullscreen
+ sleep 1
+ kodi-send -a toggleFullscreen
+ '';
+in {
+ environment.systemPackages = [htpc-launcher];
+}
diff --git a/scripts/overtid.nix b/scripts/overtid.nix
@@ -0,0 +1,36 @@
+{ pkgs, ... }:
+
+let
+ overtid = pkgs.writeShellScriptBin "overtid" ''
+ # variables
+ time_start="08:30"
+ time_end="18:00"
+ # check for arguments
+ if [ $# -eq 0 ]; then
+ printf "Decimal overtime calculator\\nUsage: %s HH:MM (HH:MM)\\n" "''${0##*/}"
+ exit 1
+ fi
+ # main logic
+ # no second variable, calculate singularly
+ if [ -z ''${2+x} ]; then
+ start="$(date -d "Yesterday $1" "+%s")"
+ if [ "''${time_start:0:2}" -gt "''${1:0:2}" ]; then
+ end="$(date -d "Yesterday $time_start" "+%s")"
+ printf "Early start: %s hours\\n" "$(date -d\@$((end - start)) -u +'scale=2; %H + %M/60' | ${pkgs.bc}/bin/bc)"
+ else
+ end="$(date -d "Yesterday $time_end" "+%s")"
+ printf "Late finish: %s hours\\n" "$(date -d\@$((start - end)) -u +'scale=2; %H + %M/60' | ${pkgs.bc}/bin/bc)"
+ fi
+ # second variable, calculate both and combine
+ else
+ start_am="$(date -d "Yesterday $1" "+%s")"
+ start_pm="$(date -d "Yesterday $2" "+%s")"
+ end_am="$(date -d "Yesterday $time_start" "+%s")"
+ end_pm="$(date -d "Yesterday $time_end" "+%s")"
+ printf "Combined overtimes: %s hours\\n" "$(echo "$(date -d\@$((end_am - start_am)) -u +'scale=2; %H + %M/60' | ${pkgs.bc}/bin/bc)" + "$(date -d\@$((start_pm - end_pm)) -u +'scale=2; %H + %M/60' | ${pkgs.bc}/bin/bc)" | ${pkgs.bc}/bin/bc)"
+ fi
+ '';
+
+in {
+ environment.systemPackages = [ overtid ];
+}
diff --git a/scripts/phone-dump.nix b/scripts/phone-dump.nix
@@ -0,0 +1,57 @@
+{pkgs, ...}: let
+ phone-dump = pkgs.writeShellScriptBin "phone-dump" ''
+ # variables
+ phone_remote=phone
+ phone_ip=$(grep -A4 "$phone_remote" "$(rclone config file | tail -1)" | awk '/host/ {print $3}')
+ destination="/mnt/pictures/personal"
+ if [ ! -d "$destination" ]; then
+ echo "Destination $destination does not exist."
+ exit 1
+ fi
+
+ # if ping phone
+ if ping -c 1 "$phone_ip" &>/dev/null; then
+ echo "Phone reachable, mounting remote"
+ directory_temp="$(mktemp -d)"
+ rclone mount "$phone_remote": "$directory_temp" --daemon
+ cd "$directory_temp" || exit
+
+ declare -a directories=(
+ "$directory_temp/DCIM/Camera"
+ "$directory_temp/Pictures"
+ "$directory_temp/Pictures/Whatsapp"
+ "$directory_temp/Android/media/com.whatsapp/WhatsApp/Media"
+ "$directory_temp/Android/media/com.whatsapp/WhatsApp/Media/WhatsApp Images"
+ "$directory_temp/Android/media/com.whatsapp/WhatsApp/Media/WhatsApp Video"
+ )
+ for i in "''${directories[@]}"
+ do
+ if [ -d "$i" ]; then
+ echo "$i"
+ find "$i" -type f -name '.nomedia*' -delete -print
+ ${pkgs.phockup}/bin/phockup "$i" "$destination/" -m
+ fi
+ done
+
+ if [ -d "$directory_temp/Pictures/Screenshots" ]; then
+ find "$directory_temp/Pictures/Screenshots" -type f -exec mv '{}' "$destination/screenshots/" -vi \;
+ fi
+
+ echo "Tidying up..."
+ find "$destination" -type f -iname 'thumbs.db' -delete -print
+ find "$destination" -type f -name '.nomedia*' -delete -print
+ find "$destination" -type d -name '.thumbnails*' -delete -print
+ find "$directory_temp" -maxdepth 2 -type d -not -path "*/\.*" -empty -delete -print 2>/dev/null
+ echo "Unmounting storage..."
+ sleep 2s
+ umount "$directory_temp" || fusermount -uz "$directory_temp"
+ echo "Deduplicating photos..."
+ ${pkgs.jdupes}/bin/jdupes "$destination" -r
+ find "/tmp/tmp.*" -maxdepth 1 -type d -not -path "*/\.*" -empty -delete -print 2>/dev/null
+ else
+ echo "Phone not reachable via ping, exiting" && exit 1
+ fi
+ '';
+in {
+ environment.systemPackages = [phone-dump];
+}
diff --git a/scripts/watchedlist.nix b/scripts/watchedlist.nix
@@ -0,0 +1,36 @@
+{ pkgs, ... }:
+
+let
+ watchedlist = pkgs.writeShellScriptBin "watchedlist" ''
+ # variables
+ if [ -z "$1" ]
+ then
+ database="watchedlist.db"
+ else
+ database="$1"
+ fi
+ # checks
+ if [ ! -f "$database" ]
+ then
+ echo Database "$database" file missing, exiting
+ exit 0
+ fi
+ # TODO: blank database check
+ # movies
+ if [ -f "movies.csv" ]; then rm movies.csv; fi
+ sqlite3 -noheader -csv $database "select title from movie_watched;" > movies.csv
+ sed -i -e 's|\"||g' -e 's|^|* |g' movies.csv
+ sort -k 2 < movies.csv > movies.md
+ rm movies.csv
+ # tv shows
+ sqlite3 -noheader -csv $database "select * from tvshows;" > tv_shows_index.csv
+ watched_id=$(sqlite3 -noheader $database "select idShow from episode_watched;" | uniq)
+ for i in $watched_id; do grep "$i" tv_shows_index.csv | cut -f2- -d, >> tv_shows.csv; done
+ sed -i -e 's|\"||g' -e 's|^|* |g' tv_shows.csv
+ sort -k 2 < tv_shows.csv > tv_shows.md
+ rm tv_shows.csv tv_shows_index.csv
+ '';
+
+in {
+ environment.systemPackages = [ watchedlist ];
+}
diff --git a/scripts/youtube-id-rss.nix b/scripts/youtube-id-rss.nix
@@ -0,0 +1,20 @@
+{ pkgs, ... }:
+
+let
+ youtube-id-rss = pkgs.writeShellScriptBin "youtube-id-rss" ''
+ if [ "$#" -eq 0 ]
+ then
+ echo "No URI argument supplied, using clipboard"
+ uri="$(wl-paste)"
+ else
+ uri="$1"
+ fi
+
+ uri_id=$(curl --silent "$uri" | tr "\"" "\n" | grep -P '^(?=.*https)(?=.*channel)' | uniq -c | sort -rn | awk 'NR==1{print $2}' )
+ base_id="$(echo "$uri_id" | awk -F "/" '{print $5}')"
+ printf "https://www.youtube.com/feeds/videos.xml?channel_id=%s\\n" "$base_id"
+ '';
+
+in {
+ environment.systemPackages = [ youtube-id-rss ];
+}