ctucx.git: nixfiles

ctucx' nixfiles

commit e879b49e15fec426065e863cc67cf33113c65ab6
parent 2afceeec588677ff5f79d3db31d3f7378d4d1ded
Author: Leah (ctucx) <leah@ctu.cx>
Date: Wed, 16 Mar 2022 19:10:27 +0100

machines/blechbuechse: add machine (+ needed changes in configurations)
20 files changed, 994 insertions(+), 55 deletions(-)
M
configurations/common.nix
|
1
-
M
configurations/programs/cli/bash.nix
|
37
+++++++++++++++++++++++++++++++++----
M
configurations/programs/cli/htop.nix
|
34
++++++++++++++++++++--------------
M
configurations/programs/cli/micro.nix
|
19
+++++++++++++------
M
configurations/programs/cli/network-utilities.nix
|
26
++++++++++++++++----------
M
configurations/programs/cli/password-store.nix
|
23
+++++++++++++++++++----
M
configurations/programs/cli/utilities.nix
|
9
++++++---
M
configurations/programs/firefox.nix
|
5
-----
M
configurations/yubikey.nix
|
38
++++++++++++++++++++++++++++++--------
A
darwin-configuration.nix
|
7
+++++++
A
darwin-rebuild
|
2
++
A
machines/blechbuechse/darwin-configuration.nix
|
166
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
machines/blechbuechse/modules/quirks.nix
|
16
++++++++++++++++
A
machines/blechbuechse/modules/syncthing.nix
|
503
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
machines/blechbuechse/services/syncthing.nix
|
20
++++++++++++++++++++
A
pkgs/bgiparser.nix
|
39
+++++++++++++++++++++++++++++++++++++++
A
pkgs/macApps/AlDente.nix
|
26
++++++++++++++++++++++++++
A
pkgs/macApps/Firefox.nix
|
26
++++++++++++++++++++++++++
A
pkgs/macApps/HiddenBar.nix
|
26
++++++++++++++++++++++++++
A
pkgs/macApps/iTerm.nix
|
26
++++++++++++++++++++++++++
diff --git a/configurations/common.nix b/configurations/common.nix
@@ -71,7 +71,6 @@
   environment.systemPackages = with pkgs; [
     alacritty.terminfo
     (pkgs.callPackage <agenix/pkgs/agenix.nix> {})
-    age
  ];
 
   users.users = {
diff --git a/configurations/programs/cli/bash.nix b/configurations/programs/cli/bash.nix
@@ -1,4 +1,4 @@
-{ pkgs, ... }:
+{ pkgs, lib, ... }:
 
 {
   imports = [

@@ -9,7 +9,7 @@
     programs = {
       bash = {
         enable               = true;
-        enableVteIntegration = true;
+        enableVteIntegration = lib.mkIf pkgs.stdenv.isLinux true;
 
         historyFileSize = 999999;
         historyIgnore   = [ "ls" "clear" "exit" ];

@@ -37,13 +37,42 @@
 
           use          = "nix-shell -p ";
 
-          "youtube-dl" = "yt-dlp";
+          youtube-dl   = "yt-dlp";
 
-          zzz          = "sleep 1 && systemctl suspend";
+          zzz          = (
+            if pkgs.stdenv.isLinux then
+              "sleep 1 && systemctl suspend"
+            else
+              "pmset sleepnow"
+          );
+
+          backgrounditems     = "bgiparser -f  \"\$HOME/Library/Application Support/com.apple.backgroundtaskmanagementagent/backgrounditems.btm\" -c";
 
           eval-system-config  = "nix-instantiate \"<nixpkgs/nixos>\" -A config.system.build.toplevel -I /etc/nixos/configuration.nix";
+
           nix-collect-garbage = "sudo nix-collect-garbage";
         };
+
+        bashrcExtra = lib.mkIf pkgs.stdenv.isDarwin ''
+          if [ "$TERM" != "dumb" ]; then
+            source "${pkgs.bash-completion}/etc/profile.d/bash_completion.sh"
+            nullglobStatus=$(shopt -p nullglob)
+            shopt -s nullglob
+            for p in $NIX_PROFILES; do
+              for m in "$p/etc/bash_completion.d/"*; do
+                source $m
+              done
+            done
+            eval "$nullglobStatus"
+            unset nullglobStatus p m
+          fi
+
+          # Make bash check its window size after a process completes
+          shopt -s checkwinsize
+
+          eval "$(/opt/homebrew/bin/brew shellenv)"
+        '';
+
       };
     };
   };
diff --git a/configurations/programs/cli/htop.nix b/configurations/programs/cli/htop.nix
@@ -14,20 +14,26 @@
       };
     };
 
-    xdg = {
-      desktopEntries = {
-        htop = {
-          name        = "Htop";
-          genericName = "Process Viewer";
-          icon        = "htop";
-          exec        = "htop";
-          terminal    = true;
-          categories  = [ "ConsoleOnly" "System" ];
-          settings    = {
-            NoDisplay = "true";
+    xdg = (
+      if pkgs.stdenv.isLinux then
+        {
+          desktopEntries = {
+            htop = {
+              name        = "Htop";
+              genericName = "Process Viewer";
+              icon        = "htop";
+              exec        = "htop";
+              terminal    = true;
+              categories  = [ "ConsoleOnly" "System" ];
+              settings    = {
+                NoDisplay = "true";
+              };
+            };
           };
-        };
-      };
-    };
+        }
+      else
+        {}
+    );
+
   };
 }
diff --git a/configurations/programs/cli/micro.nix b/configurations/programs/cli/micro.nix
@@ -1,13 +1,20 @@
-{ pkgs, ... }:
+{ pkgs, lib, ... }:
 
 {
   home-manager.users.leah = {
     home = {
-      packages = [
-        (pkgs.micro.overrideAttrs(oldAttrs: {
-          postInstall = "";
-        }))
-      ];
+      packages = (
+        if pkgs.stdenv.isLinux then
+          [
+            (pkgs.micro.overrideAttrs(oldAttrs: {
+              postInstall = "";
+            }))
+          ]
+        else 
+          [
+            pkgs.micro
+          ]
+      );
 
       sessionVariables = {
         EDITOR        = "micro";
diff --git a/configurations/programs/cli/network-utilities.nix b/configurations/programs/cli/network-utilities.nix
@@ -1,17 +1,23 @@
-{ pkgs, ... }:
+{ pkgs, lib, ... }:
 
 {
-  programs.mtr.enable        = true;
-  programs.traceroute.enable = true;
+
+  programs.mtr.enable        = (if pkgs.stdenv.isLinux then true else false);
+  programs.traceroute.enable = (if pkgs.stdenv.isLinux then true else false);
 
   home-manager.users.leah = {
-    home = {
-      packages = with pkgs; [
-        dnsutils
-        whois
-        nmap-unfree
-        tcpdump
-      ];
+    home.packages = with pkgs; [
+      dnsutils
+      whois
+      nmap-unfree
+      tcpdump
+    ] ++ (if pkgs.stdenv.isDarwin then [
+      mtr
+    ] else []);
+
+    programs.bash.shellAliases = lib.mkIf pkgs.stdenv.isDarwin {
+      mtr = "sudo mtr";
     };
   };
+
 }
diff --git a/configurations/programs/cli/password-store.nix b/configurations/programs/cli/password-store.nix
@@ -2,10 +2,25 @@
 
 {
   home-manager.users.leah = {
-    home.packages           = [ pkgs.pwgen ];
-    programs.password-store = {
-      enable  = true;
-      package = pkgs.pass.withExtensions (exts: [ exts.pass-otp exts.pass-audit exts.pass-update exts.pass-genphrase]);
+    home = {
+      packages         = [ pkgs.pwgen ];
+      sessionVariables = {
+        PASSWORD_STORE_DIR = "\$HOME/.local/share/password-store";
+      };
     };
+
+    programs = {
+      password-store = {
+        enable  = true;
+        package = pkgs.pass.withExtensions (exts: [ exts.pass-otp exts.pass-audit exts.pass-update exts.pass-genphrase]);
+      };
+
+      browserpass = {
+        enable   = true;
+        browsers = [ "firefox" ];
+      };
+
+    };
+
   };
 }
diff --git a/configurations/programs/cli/utilities.nix b/configurations/programs/cli/utilities.nix
@@ -15,18 +15,21 @@
         fd
         file
 
-        niv
+        age
 
         unzip
 
+        smartmontools
+      ] ++ (if pkgs.stdenv.isLinux then [
+        niv
+
         usbutils
         pciutils
 
         lm_sensors
 
         gptfdisk
-        smartmontools
-      ];
+      ] else []);
     };
 
     programs = {
diff --git a/configurations/programs/firefox.nix b/configurations/programs/firefox.nix
@@ -42,11 +42,6 @@ in {
         enable = true;
         package = pkgs.firefox;
       };
-
-      browserpass = {
-        enable   = true;
-        browsers = [ "firefox" ];
-      };
     };
 
     wayland.windowManager.sway.extraConfig = ''
diff --git a/configurations/yubikey.nix b/configurations/yubikey.nix
@@ -1,15 +1,31 @@
-{ config, pkgs, ... }:
+{ config, pkgs, lib, ... }:
 
 {
-  services.pcscd.enable  = true;
-  services.udev.packages = with pkgs; [ libu2f-host yubikey-personalization ];
-  services.dbus.packages = with pkgs; [ gcr ];
+  services = {
+    pcscd.enable  = (if pkgs.stdenv.isLinux then true else false);
+    udev.packages = (if pkgs.stdenv.isLinux then (with pkgs; [ libu2f-host yubikey-personalization ]) else []);
+    dbus.packages = (if pkgs.stdenv.isLinux then (with pkgs; [ gcr ]) else []);
+  };
 
   home-manager.users.leah = {
     home = {
-      packages         = [ pkgs.pcsctools ];
+      packages = lib.mkIf pkgs.stdenv.isLinux [ pkgs.pcsctools ];
+
       sessionVariables = {
-        SSH_AUTH_SOCK = "/run/user/1000/gnupg/S.gpg-agent.ssh";
+        GNUPGHOME     = "$HOME/.gnupg";
+        SSH_AUTH_SOCK = (
+          if pkgs.stdenv.isLinux then
+            "/run/user/1000/gnupg/S.gpg-agent.ssh"
+          else
+            null
+        );
+      };
+
+      file = lib.mkIf pkgs.stdenv.isDarwin {
+        ".gnupg/gpg-agent.conf".text = ''
+          enable-ssh-support
+          pinentry-program ${pkgs.pinentry_mac}/Applications/pinentry-mac.app/Contents/MacOS/pinentry-mac
+        '';
       };
     };
 

@@ -25,7 +41,7 @@
           keyserver = "hkps://keyserver.ubuntu.com:443";
         };
 
-        scdaemonSettings = {
+        scdaemonSettings = lib.mkIf pkgs.stdenv.isLinux {
           disable-ccid = true;
         };
       };

@@ -38,13 +54,19 @@
       };
 
       bash = {
+        initExtra = lib.mkIf pkgs.stdenv.isDarwin ''
+          export GPG_TTY=$(tty)
+          export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket)
+          gpgconf --launch gpg-agent
+        '';
+
         shellAliases = {
           gpg-card-relearn = "gpg-connect-agent 'scd serialno' 'learn --force' /bye";
         };
       };
 	};
 
-    services = {
+    services = lib.mkIf pkgs.stdenv.isLinux {
       gpg-agent = {
         enable             = true;
         enableSshSupport   = true;
diff --git a/darwin-configuration.nix b/darwin-configuration.nix
@@ -0,0 +1,7 @@
+{ config, pkgs, ... }:
+
+{
+  imports = [
+    ./machines/blechbuechse/darwin-configuration.nix
+  ];
+}
diff --git a/darwin-rebuild b/darwin-rebuild
@@ -0,0 +1,2 @@
+#!/usr/bin/env sh
+darwin-rebuild -I $(nix-build /Users/leah/nixfiles/nix/sources-dir.nix --no-out-link) "$@"
diff --git a/machines/blechbuechse/darwin-configuration.nix b/machines/blechbuechse/darwin-configuration.nix
@@ -0,0 +1,166 @@
+{ config, pkgs, ... }:
+
+let
+  bgiparser = pkgs.callPackage ../../pkgs/bgiparser.nix {};
+
+  Firefox   = pkgs.callPackage ../../pkgs/macApps/Firefox.nix {};
+  iTerm     = pkgs.callPackage ../../pkgs/macApps/iTerm.nix {};
+  HiddenBar = pkgs.callPackage ../../pkgs/macApps/HiddenBar.nix {};
+  AlDente   = pkgs.callPackage ../../pkgs/macApps/AlDente.nix {};
+
+in {
+
+  imports = [
+    <home-manager/nix-darwin>
+    ./modules/syncthing.nix
+    ./modules/quirks.nix
+    ./services/syncthing.nix
+    ../../configurations/yubikey.nix
+    ../../configurations/programs/cli/bash.nix
+    ../../configurations/programs/cli/micro.nix
+    ../../configurations/programs/cli/ssh.nix
+    ../../configurations/programs/cli/git.nix
+    ../../configurations/programs/cli/tmux.nix
+    ../../configurations/programs/cli/htop.nix
+    ../../configurations/programs/cli/password-store.nix
+    ../../configurations/programs/cli/utilities.nix
+    ../../configurations/programs/cli/network-utilities.nix
+    ../../configurations/programs/cli/scripts.nix
+  ];
+
+  nix.package = pkgs.nix;
+  services.nix-daemon.enable = true;
+
+  home-manager = {
+    useUserPackages = true;
+
+    users.leah.home.packages = [ bgiparser ];
+  };
+
+  environment = {
+    darwinConfig   = "\$HOME/nixfiles/darwin-configuration.nix";
+    loginShell     = "${pkgs.bashInteractive}/bin/bash";
+    shells         = [ pkgs.bashInteractive ];
+    systemPackages = with pkgs; [
+      bashInteractive
+      Firefox
+      iTerm
+      HiddenBar
+      AlDente
+    ];
+  };
+
+  networking.hostName     = "blechbuechse";
+  networking.computerName = config.networking.hostName;
+
+  system.keyboard = {
+    enableKeyMapping = true;
+    userKeyMapping   = [
+      # Remap tilde on non-US keyboards
+      { HIDKeyboardModifierMappingSrc = 30064771172; HIDKeyboardModifierMappingDst = 30064771125; }
+      # Swap right command and option keys
+      { HIDKeyboardModifierMappingSrc = 30064771303; HIDKeyboardModifierMappingDst = 30064771302; }
+      { HIDKeyboardModifierMappingSrc = 30064771302; HIDKeyboardModifierMappingDst = 30064771303; }
+    ];
+  };
+
+  system.activationScripts.extraSystemSettings.text = ''
+    # Sleep display after 30 minutes
+    sudo pmset -a displaysleep 30
+  '';
+
+  system.activationScripts.extraUserActivation.text = ''
+    # Set accent color to green
+    defaults write .GlobalPreferences AppleAccentColor -int 3
+
+    # Show battery percentage in menubar
+    defaults write ~/Library/Preferences/ByHost/com.apple.controlcenter.plist BatteryShowPercentage -bool true
+
+    # Show sound-applet always in menubar
+    defaults write /Users/leah/Library/Preferences/ByHost/com.apple.controlcenter.plist Sound -int 16
+
+    # Set menubar clock format
+    defaults write com.apple.menuextra.clock "DateFormat" -string "\"d MMM HH:mm:ss\""
+
+    # Flash clock time seperators
+    defaults write com.apple.menuextra.clock "FlashDateSeparators" -bool "true"
+
+    # Set defaults for panes in Finder's "Get Info"
+    defaults write com.apple.finder FXInfoPanesExpanded -dict \
+      General -bool true \
+      OpenWith -bool true \
+      Privileges -bool true \
+      Preview -bool false
+
+    # Disable iTerm's annoying promt when quitting it
+    defaults write com.googlecode.iterm2 PromptOnQuit -bool false
+
+    # Disable RichText in TextEdit
+    defaults write com.apple.TextEdit RichText -int 0
+
+    # Open and save files as UTF-8 in TextEdit
+    defaults write com.apple.TextEdit PlainTextEncoding -int 4
+    defaults write com.apple.TextEdit PlainTextEncodingForWrite -int 4
+
+    # Prevent Photos from opening automatically when devices are plugged in
+    defaults -currentHost write com.apple.ImageCapture disableHotPlug -bool true
+  '';
+
+  system.defaults = {
+    NSGlobalDomain = {
+      AppleInterfaceStyle                      = "Dark";
+      AppleInterfaceStyleSwitchesAutomatically = false;
+
+      AppleTemperatureUnit   = "Celsius";
+      AppleMeasurementUnits  = "Centimeters";
+      AppleMetricUnits       = 1;
+      AppleShowAllExtensions = true;
+
+      "com.apple.sound.beep.volume"       = "0.6";
+
+      NSTableViewDefaultSizeMode          = 2;
+      NSDocumentSaveNewDocumentsToCloud   = false;
+      NSNavPanelExpandedStateForSaveMode  = true;
+      NSNavPanelExpandedStateForSaveMode2 = true;
+    };
+
+    dock = {
+      tilesize                = 50;
+      minimize-to-application = true;
+      show-recents            = false;
+
+      # Disable all hot corners
+      wvous-tl-corner         = 1;
+      wvous-tr-corner         = 1;
+      wvous-bl-corner         = 1;
+      wvous-br-corner         = 1;
+    };
+
+    finder = {
+      AppleShowAllExtensions         = true;
+      CreateDesktop                  = false;
+      ShowStatusBar                  = true;
+      ShowPathbar                    = false;
+      FXPreferredViewStyle           = "Nlsv";
+      FXDefaultSearchScope           = "SCcf";
+      FXEnableExtensionChangeWarning = false;
+    };
+
+    screencapture = {
+      location = "/Users/leah/Pictures/Screenshots";
+    };
+
+    smb = {
+      NetBIOSName       = config.networking.hostName;
+      ServerDescription = config.networking.hostName;
+    };
+
+    trackpad = {
+      FirstClickThreshold  = 1;
+      SecondClickThreshold = 2;
+    };
+
+  };
+
+  system.stateVersion = 4;
+}
diff --git a/machines/blechbuechse/modules/quirks.nix b/machines/blechbuechse/modules/quirks.nix
@@ -0,0 +1,16 @@
+{ config, lib, pkgs, ... }:
+
+{
+
+  options = {
+    programs.mtr.enable        = lib.mkEnableOption "Polyfill option to fix weird bug in nix-darwin?";
+    programs.traceroute.enable = lib.mkEnableOption "Polyfill option to fix weird bug in nix-darwin?";
+
+    services.dbus.enable       = lib.mkEnableOption "Polyfill option to fix weird bug in nix-darwin?";
+    services.pcscd.enable      = lib.mkEnableOption "Polyfill option to fix weird bug in nix-darwin?";
+
+    services.dbus.packages     = lib.mkOption { type = lib.types.listOf lib.types.path; };
+    services.udev.packages     = lib.mkOption { type = lib.types.listOf lib.types.path; };
+  };
+
+}
diff --git a/machines/blechbuechse/modules/syncthing.nix b/machines/blechbuechse/modules/syncthing.nix
@@ -0,0 +1,503 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.syncthing;
+  defaultUser = "syncthing";
+  defaultGroup = defaultUser;
+
+  devices = mapAttrsToList (name: device: {
+    deviceID = device.id;
+    inherit (device) name addresses introducer autoAcceptFolders;
+  }) cfg.devices;
+
+  folders = mapAttrsToList ( _: folder: {
+    inherit (folder) path id label type;
+    devices = map (device: { deviceId = cfg.devices.${device}.id; }) folder.devices;
+    rescanIntervalS = folder.rescanInterval;
+    fsWatcherEnabled = folder.watch;
+    fsWatcherDelayS = folder.watchDelay;
+    ignorePerms = folder.ignorePerms;
+    ignoreDelete = folder.ignoreDelete;
+    versioning = folder.versioning;
+  }) (filterAttrs (
+    _: folder:
+    folder.enable
+  ) cfg.folders);
+
+  updateConfig = pkgs.writers.writeDash "merge-syncthing-config" ''
+    set -efu
+
+    # get the api key by parsing the config.xml
+    while
+        ! api_key=$(${pkgs.libxml2}/bin/xmllint \
+            --xpath 'string(configuration/gui/apikey)' \
+            "${cfg.configDir}/config.xml")
+    do sleep 1; done
+
+    curl() {
+        ${pkgs.curl}/bin/curl -sSLk -H "X-API-Key: $api_key" \
+            --retry 1000 --retry-delay 1 --retry-all-errors \
+            "$@"
+    }
+
+    # query the old config
+    old_cfg=$(curl ${cfg.guiAddress}/rest/config)
+
+    # generate the new config by merging with the NixOS config options
+    new_cfg=$(printf '%s\n' "$old_cfg" | ${pkgs.jq}/bin/jq -c '. * {
+        "devices": (${builtins.toJSON devices}${optionalString (! cfg.overrideDevices) " + .devices"}),
+        "folders": (${builtins.toJSON folders}${optionalString (! cfg.overrideFolders) " + .folders"})
+    } * ${builtins.toJSON cfg.extraOptions}')
+
+    # send the new config
+    curl -X PUT -d "$new_cfg" ${cfg.guiAddress}/rest/config
+
+    # restart Syncthing if required
+    if curl ${cfg.guiAddress}/rest/config/restart-required |
+       ${pkgs.jq}/bin/jq -e .requiresRestart > /dev/null; then
+        curl -X POST ${cfg.guiAddress}/rest/system/restart
+    fi
+  '';
+in {
+  ###### interface
+  options = {
+    services.syncthing = {
+
+      enable = mkEnableOption
+        "Syncthing, a self-hosted open-source alternative to Dropbox and Bittorrent Sync";
+
+      cert = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Path to the <literal>cert.pem</literal> file, which will be copied into Syncthing's
+          <link linkend="opt-services.syncthing.configDir">configDir</link>.
+        '';
+      };
+
+      key = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Path to the <literal>key.pem</literal> file, which will be copied into Syncthing's
+          <link linkend="opt-services.syncthing.configDir">configDir</link>.
+        '';
+      };
+
+      overrideDevices = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to delete the devices which are not configured via the
+          <link linkend="opt-services.syncthing.devices">devices</link> option.
+          If set to <literal>false</literal>, devices added via the web
+          interface will persist and will have to be deleted manually.
+        '';
+      };
+
+      devices = mkOption {
+        default = {};
+        description = ''
+          Peers/devices which Syncthing should communicate with.
+
+          Note that you can still add devices manually, but those changes
+          will be reverted on restart if <link linkend="opt-services.syncthing.overrideDevices">overrideDevices</link>
+          is enabled.
+        '';
+        example = {
+          bigbox = {
+            id = "7CFNTQM-IMTJBHJ-3UWRDIU-ZGQJFR6-VCXZ3NB-XUH3KZO-N52ITXR-LAIYUAU";
+            addresses = [ "tcp://192.168.0.10:51820" ];
+          };
+        };
+        type = types.attrsOf (types.submodule ({ name, ... }: {
+          options = {
+
+            name = mkOption {
+              type = types.str;
+              default = name;
+              description = ''
+                The name of the device.
+              '';
+            };
+
+            addresses = mkOption {
+              type = types.listOf types.str;
+              default = [];
+              description = ''
+                The addresses used to connect to the device.
+                If this is left empty, dynamic configuration is attempted.
+              '';
+            };
+
+            id = mkOption {
+              type = types.str;
+              description = ''
+                The device ID. See <link xlink:href="https://docs.syncthing.net/dev/device-ids.html"/>.
+              '';
+            };
+
+            introducer = mkOption {
+              type = types.bool;
+              default = false;
+              description = ''
+                Whether the device should act as an introducer and be allowed
+                to add folders on this computer.
+                See <link xlink:href="https://docs.syncthing.net/users/introducer.html"/>.
+              '';
+            };
+
+            autoAcceptFolders = mkOption {
+              type = types.bool;
+              default = false;
+              description = ''
+                Automatically create or share folders that this device advertises at the default path.
+                See <link xlink:href="https://docs.syncthing.net/users/config.html?highlight=autoaccept#config-file-format"/>.
+              '';
+            };
+
+          };
+        }));
+      };
+
+      overrideFolders = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to delete the folders which are not configured via the
+          <link linkend="opt-services.syncthing.folders">folders</link> option.
+          If set to <literal>false</literal>, folders added via the web
+          interface will persist and will have to be deleted manually.
+        '';
+      };
+
+      folders = mkOption {
+        default = {};
+        description = ''
+          Folders which should be shared by Syncthing.
+
+          Note that you can still add devices manually, but those changes
+          will be reverted on restart if <link linkend="opt-services.syncthing.overrideDevices">overrideDevices</link>
+          is enabled.
+        '';
+        example = literalExpression ''
+          {
+            "/home/user/sync" = {
+              id = "syncme";
+              devices = [ "bigbox" ];
+            };
+          }
+        '';
+        type = types.attrsOf (types.submodule ({ name, ... }: {
+          options = {
+
+            enable = mkOption {
+              type = types.bool;
+              default = true;
+              description = ''
+                Whether to share this folder.
+                This option is useful when you want to define all folders
+                in one place, but not every machine should share all folders.
+              '';
+            };
+
+            path = mkOption {
+              type = types.str;
+              default = name;
+              description = ''
+                The path to the folder which should be shared.
+              '';
+            };
+
+            id = mkOption {
+              type = types.str;
+              default = name;
+              description = ''
+                The ID of the folder. Must be the same on all devices.
+              '';
+            };
+
+            label = mkOption {
+              type = types.str;
+              default = name;
+              description = ''
+                The label of the folder.
+              '';
+            };
+
+            devices = mkOption {
+              type = types.listOf types.str;
+              default = [];
+              description = ''
+                The devices this folder should be shared with. Each device must
+                be defined in the <link linkend="opt-services.syncthing.devices">devices</link> option.
+              '';
+            };
+
+            versioning = mkOption {
+              default = null;
+              description = ''
+                How to keep changed/deleted files with Syncthing.
+                There are 4 different types of versioning with different parameters.
+                See <link xlink:href="https://docs.syncthing.net/users/versioning.html"/>.
+              '';
+              example = literalExpression ''
+                [
+                  {
+                    versioning = {
+                      type = "simple";
+                      params.keep = "10";
+                    };
+                  }
+                  {
+                    versioning = {
+                      type = "trashcan";
+                      params.cleanoutDays = "1000";
+                    };
+                  }
+                  {
+                    versioning = {
+                      type = "staggered";
+                      params = {
+                        cleanInterval = "3600";
+                        maxAge = "31536000";
+                        versionsPath = "/syncthing/backup";
+                      };
+                    };
+                  }
+                  {
+                    versioning = {
+                      type = "external";
+                      params.versionsPath = pkgs.writers.writeBash "backup" '''
+                        folderpath="$1"
+                        filepath="$2"
+                        rm -rf "$folderpath/$filepath"
+                      ''';
+                    };
+                  }
+                ]
+              '';
+              type = with types; nullOr (submodule {
+                options = {
+                  type = mkOption {
+                    type = enum [ "external" "simple" "staggered" "trashcan" ];
+                    description = ''
+                      The type of versioning.
+                      See <link xlink:href="https://docs.syncthing.net/users/versioning.html"/>.
+                    '';
+                  };
+                  params = mkOption {
+                    type = attrsOf (either str path);
+                    description = ''
+                      The parameters for versioning. Structure depends on
+                      <link linkend="opt-services.syncthing.folders._name_.versioning.type">versioning.type</link>.
+                      See <link xlink:href="https://docs.syncthing.net/users/versioning.html"/>.
+                    '';
+                  };
+                };
+              });
+            };
+
+            rescanInterval = mkOption {
+              type = types.int;
+              default = 3600;
+              description = ''
+                How often the folder should be rescanned for changes.
+              '';
+            };
+
+            type = mkOption {
+              type = types.enum [ "sendreceive" "sendonly" "receiveonly" ];
+              default = "sendreceive";
+              description = ''
+                Whether to only send changes for this folder, only receive them
+                or both.
+              '';
+            };
+
+            watch = mkOption {
+              type = types.bool;
+              default = true;
+              description = ''
+                Whether the folder should be watched for changes by inotify.
+              '';
+            };
+
+            watchDelay = mkOption {
+              type = types.int;
+              default = 10;
+              description = ''
+                The delay after an inotify event is triggered.
+              '';
+            };
+
+            ignorePerms = mkOption {
+              type = types.bool;
+              default = true;
+              description = ''
+                Whether to ignore permission changes.
+              '';
+            };
+
+            ignoreDelete = mkOption {
+              type = types.bool;
+              default = false;
+              description = ''
+                Whether to skip deleting files that are deleted by peers.
+                See <link xlink:href="https://docs.syncthing.net/advanced/folder-ignoredelete.html"/>.
+              '';
+            };
+          };
+        }));
+      };
+
+      extraOptions = mkOption {
+        type = types.addCheck (pkgs.formats.json {}).type isAttrs;
+        default = {};
+        description = ''
+          Extra configuration options for Syncthing.
+          See <link xlink:href="https://docs.syncthing.net/users/config.html"/>.
+        '';
+        example = {
+          options.localAnnounceEnabled = false;
+          gui.theme = "black";
+        };
+      };
+
+      guiAddress = mkOption {
+        type = types.str;
+        default = "127.0.0.1:8384";
+        description = ''
+          The address to serve the web interface at.
+        '';
+      };
+
+      systemService = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to auto-launch Syncthing as a system service.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = defaultUser;
+        example = "yourUser";
+        description = ''
+          The user to run Syncthing as.
+          By default, a user named <literal>${defaultUser}</literal> will be created.
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = defaultGroup;
+        example = "yourGroup";
+        description = ''
+          The group to run Syncthing under.
+          By default, a group named <literal>${defaultGroup}</literal> will be created.
+        '';
+      };
+
+      dataDir = mkOption {
+        type = types.path;
+        default = "/var/lib/syncthing";
+        example = "/home/yourUser";
+        description = ''
+          The path where synchronised directories will exist.
+        '';
+      };
+
+      configDir = let
+        cond = versionAtLeast config.system.stateVersion "19.03";
+      in mkOption {
+        type = types.path;
+        description = ''
+          The path where the settings and keys will exist.
+        '';
+        default = "/Users/${cfg.user}/Library/Application Support/Syncthing";
+        defaultText = literalExpression "dataDir${optionalString cond " + \"/.config/syncthing\""}";
+      };
+
+      extraFlags = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "--reset-deltas" ];
+        description = ''
+          Extra flags passed to the syncthing command in the service definition.
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.syncthing;
+        defaultText = literalExpression "pkgs.syncthing";
+        description = ''
+          The Syncthing package to use.
+        '';
+      };
+    };
+  };
+
+  imports = [
+    (mkRemovedOptionModule [ "services" "syncthing" "useInotify" ] ''
+      This option was removed because Syncthing now has the inotify functionality included under the name "fswatcher".
+      It can be enabled on a per-folder basis through the web interface.
+    '')
+  ] ++ map (o:
+    mkRenamedOptionModule [ "services" "syncthing" "declarative" o ] [ "services" "syncthing" o ]
+  ) [ "cert" "key" "devices" "folders" "overrideDevices" "overrideFolders" "extraOptions"];
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ pkgs.syncthing ];
+
+    users.users = mkIf (cfg.systemService && cfg.user == defaultUser) {
+      ${defaultUser} =
+        { group = cfg.group;
+          home  = cfg.dataDir;
+          createHome = true;
+          uid = config.ids.uids.syncthing;
+          description = "Syncthing daemon user";
+        };
+    };
+
+    users.groups = mkIf (cfg.systemService && cfg.group == defaultGroup) {
+      ${defaultGroup}.gid =
+        config.ids.gids.syncthing;
+    };
+
+    launchd.daemons.syncthing = lib.mkIf cfg.systemService {
+      path = [ pkgs.syncthing ];
+
+      environment = {
+        STNORESTART = "yes";
+        STNOUPGRADE = "yes";
+        HOME        = "/Users/${cfg.user}";
+      };
+
+      script    = ''
+        install -dm700 -o ${cfg.user} -g ${cfg.group} ${cfg.configDir}
+        ${optionalString (cfg.cert != null) "install -Dm400 -o ${cfg.user} -g ${cfg.group} ${toString cfg.cert} ${cfg.configDir}/cert.pem"}
+        ${optionalString (cfg.key != null) "install -Dm400 -o ${cfg.user} -g ${cfg.group} ${toString cfg.key} ${cfg.configDir}/key.pem"}
+        ${optionalString (cfg.devices != {} || cfg.folders != {} || cfg.extraOptions != {}) "${updateConfig}&" }
+        syncthing -no-browser -gui-address=${cfg.guiAddress} -home="${cfg.configDir}" ${escapeShellArgs cfg.extraFlags}
+      '';
+
+      serviceConfig = {
+        UserName          = cfg.user;
+        GroupName         = cfg.group;
+        KeepAlive         = true;
+        LowPriorityIO     = true;
+        ProcessType       = "Background";
+        StandardOutPath   = "/Users/${cfg.user}/Library/Logs/Syncthing.log";
+        StandardErrorPath = "/Users/${cfg.user}/Library/Logs/Syncthing-Errors.log";          
+      };
+    };
+  };
+}
diff --git a/machines/blechbuechse/services/syncthing.nix b/machines/blechbuechse/services/syncthing.nix
@@ -0,0 +1,20 @@
+{ pkgs, config, lib, ... }:
+
+let
+  syncthingConfig = import ../../../configurations/syncthing.nix { inherit pkgs; inherit config; inherit lib; };
+
+in {
+
+  services = {
+    syncthing = {
+      enable  = true;
+
+      user    = "leah";
+      group   = "staff";
+
+      dataDir = syncthingConfig.dataDir;
+      devices = syncthingConfig.devices;
+      folders = syncthingConfig.folders;
+    };
+  };
+}
diff --git a/pkgs/bgiparser.nix b/pkgs/bgiparser.nix
@@ -0,0 +1,39 @@
+{ pkgs, stdenv, ... }:
+
+stdenv.mkDerivation {
+  name = "bgiparser";
+
+  buildInputs = [ pkgs.python3 ];
+
+  src = [
+    (pkgs.fetchFromGitHub {
+      name   = "bgiparser";
+      owner  = "mnrkbys";
+      repo   = "bgiparser";
+      rev    = "c9f7443cbe59f9af933e09f95fea804bbd9175f7";
+      sha256 = "0aa433pr09ins99g21bj2yp0p3h282cki9j1yxi27rln3cdyhcaq";
+    })
+    (pkgs.fetchFromGitHub {
+      name   = "ccl-bplist";
+      owner  = "cclgroupltd";
+      repo   = "ccl-bplist";
+      rev    = "76d04b7fc20c403f27248d8dcae646b524cdcc0a";
+      sha256 = "0br0r2gmwmdibla45mildhyf1mmyywxzw4yxd208qqlmzhiab7kk";
+    })
+  ];
+
+  sourceRoot = ".";
+
+  installPhase = ''
+    mkdir -p "$out/bin";
+    mkdir -p "$out/lib";
+
+    cp "./ccl-bplist/ccl_bplist.py"          "$out/lib/ccl_bplist.py";
+    cp "./bgiparser/bgiparser_foundation.py" "$out/lib/bgiparser_foundation.py";
+    cp "./bgiparser/bgiparser.py"            "$out/lib/bgiparser.py";
+
+    ln -s "$out/lib/bgiparser.py"            "$out/bin/bgiparser";
+
+    chmod +x "$out/lib/bgiparser.py";
+  '';
+}
diff --git a/pkgs/macApps/AlDente.nix b/pkgs/macApps/AlDente.nix
@@ -0,0 +1,26 @@
+{ stdenv, fetchurl, undmg, ... }:
+
+stdenv.mkDerivation rec {
+  pname   = "AlDente";
+  version = "1.15.2";
+
+  buildInputs  = [ undmg ];
+  sourceRoot   = ".";
+  phases       = [ "unpackPhase" "installPhase" ];
+  installPhase = ''
+    mkdir -p "$out/Applications"
+    cp -r AlDente.app "$out/Applications/AlDente.app"
+  '';
+
+  src = fetchurl {
+    name   = "AlDente-${version}.dmg";
+    url    = "https://github.com/davidwernhart/AlDente/releases/download/${version}/AlDente.dmg";
+    sha256 = "1bdfnh295dr1hrz12p2yjy86rr4lbghhs3f7wy7d73yzc2j5i7g2";
+  };
+
+#  meta = with stdenv.lib; {
+#    description = "macOS tool to limit maximum charging percentage";
+#    homepage    = "https://github.com/davidwernhart/AlDente";
+#    platforms   = platforms.darwin;
+#  };
+}
diff --git a/pkgs/macApps/Firefox.nix b/pkgs/macApps/Firefox.nix
@@ -0,0 +1,26 @@
+{ stdenv, fetchurl, undmg, ... }:
+
+stdenv.mkDerivation rec {
+  pname   = "Firefox";
+  version = "98.0.1";
+
+  buildInputs  = [ undmg ];
+  sourceRoot   = ".";
+  phases       = [ "unpackPhase" "installPhase" ];
+  installPhase = ''
+    mkdir -p "$out/Applications"
+    cp -r Firefox.app "$out/Applications/Firefox.app"
+  '';
+
+  src = fetchurl {
+    name   = "Firefox-${version}.dmg";
+    url    = "https://download-installer.cdn.mozilla.net/pub/firefox/releases/${version}/mac/en-US/Firefox%20${version}.dmg";
+    sha256 = "1jxm6hm8ll0agdvkfd432g71azkmv10yhf8zd7m20igpcih3wzcc";
+  };
+
+#  meta = with stdenv.lib; {
+#    description = "The Firefox web browser";
+#    homepage    = "https://www.mozilla.org/en-GB/firefox";
+#    platforms   = platforms.darwin;
+#  };
+}
diff --git a/pkgs/macApps/HiddenBar.nix b/pkgs/macApps/HiddenBar.nix
@@ -0,0 +1,26 @@
+{ stdenv, fetchurl, undmg, ... }:
+
+stdenv.mkDerivation rec {
+  pname   = "HiddenBar";
+  version = "1.9";
+
+  buildInputs  = [ undmg ];
+  sourceRoot   = ".";
+  phases       = [ "unpackPhase" "installPhase" ];
+  installPhase = ''
+    mkdir -p "$out/Applications"
+    cp -r "Hidden Bar.app" "$out/Applications/Hidden Bar.app"
+  '';
+
+  src = fetchurl {
+    name   = "Hidden.Bar.${version}.dmg";
+    url    = "https://github.com/dwarvesf/hidden/releases/download/v${version}/Hidden.Bar.${version}.dmg";
+    sha256 = "00kdnb5639i2mqgmcwby2izb9282f4a91qzbib0hpi61yljb0m1z";
+  };
+
+#  meta = with stdenv.lib; {
+#    description = "An ultra-light MacOS utility that helps hide menu bar icons ";
+#    homepage    = "https://github.com/dwarvesf/hidden";
+#    platforms   = platforms.darwin;
+#  };
+}
diff --git a/pkgs/macApps/iTerm.nix b/pkgs/macApps/iTerm.nix
@@ -0,0 +1,26 @@
+{ stdenv, fetchurl, unzip, ... }:
+
+stdenv.mkDerivation rec {
+  pname   = "iTerm";
+  version = "3_4_15";
+
+  buildInputs  = [ unzip ];
+  sourceRoot   = ".";
+  phases       = [ "unpackPhase" "installPhase" ];
+  installPhase = ''
+    mkdir -p "$out/Applications"
+    cp -r iTerm.app "$out/Applications/iTerm.app"
+  '';
+
+  src = fetchurl {
+    name   = "iTerm-${version}.zip";
+    url    = "https://iterm2.com/downloads/stable/iTerm2-${version}.zip";
+    sha256 = "0y0vhxwn1cl0y1gjm5fad5zndb4v448mqcksbmmskpgg73h4wn9j";
+  };
+
+#  meta = with stdenv.lib; {
+#    description = "iTerm2 is a terminal emulator for Mac OS X that does amazing things.";
+#    homepage    = "https://iterm2.com/";
+#    platforms   = platforms.darwin;
+#  };
+}