diff --git a/.drone.yml b/.drone.yml index 7ad1c78..b192230 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,24 +1,27 @@ +--- kind: pipeline -name: check config +type: exec +name: NixOS config check steps: - - name: format check - image: nixos/nix - commands: - - nix-shell -p nixpkgs-fmt --run 'nixpkgs-fmt . --check' +- name: nix flake check + commands: + - nix flake check - - name: notify - image: plugins/matrix - settings: - homeserver: - from_secret: matrix_homeserver - roomid: - from_secret: matrix_roomid - username: - from_secret: matrix_username - password: - from_secret: matrix_password - trigger: - status: - - failure - - success +- name: notifiy + commands: + - nix run .#matrix-notifier + environment: + ADDRESS: + from_secret: matrix_homeserver + ROOM: + from_secret: matrix_roomid + USER: + from_secret: matrix_username + PASS: + from_secret: matrix_password + when: + status: + - failure + - success +... diff --git a/.envrc b/.envrc index 9556665..4b297a5 100644 --- a/.envrc +++ b/.envrc @@ -6,3 +6,4 @@ use_flake() { ulimit -s unlimited # Bypass current bug in `nix` flakes evaluation use flake +eval "$shellHooks" diff --git a/.git-crypt/.gitattributes b/.git-crypt/.gitattributes deleted file mode 100644 index 665b10e..0000000 --- a/.git-crypt/.gitattributes +++ /dev/null @@ -1,4 +0,0 @@ -# Do not edit this file. To specify the files to encrypt, create your own -# .gitattributes file in the directory where your files are. -* !filter !diff -*.gpg binary diff --git a/.git-crypt/keys/default/0/06B6C818917564FD1014DD342E4F270130BBF854.gpg b/.git-crypt/keys/default/0/06B6C818917564FD1014DD342E4F270130BBF854.gpg deleted file mode 100644 index c941bd5..0000000 Binary files a/.git-crypt/keys/default/0/06B6C818917564FD1014DD342E4F270130BBF854.gpg and /dev/null differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fcf7246 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/.pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index 0543253..0000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,21 +0,0 @@ -repos: -- repo: 'https://github.com/pre-commit/pre-commit-hooks' - rev: 'v2.3.0' - hooks: - - id: 'trailing-whitespace' - - id: 'end-of-file-fixer' - - id: 'check-yaml' - - id: 'check-added-large-files' -- repo: 'https://github.com/jumanjihouse/pre-commit-hooks' - rev: '2.1.4' - hooks: - - id: 'forbid-binary' -- repo: 'local' - hooks: - - id: 'nixpkgs-fmt' - name: 'nixpkgs-fmt' - description: 'Format nix code with nixpkgs-fmt' - entry: 'nixpkgs-fmt' - language: 'system' - files: '\.nix$' - always_run: true diff --git a/README.md b/README.md index 8d84109..3aa6453 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,8 @@ Secondly, take care of a few manual steps: * Configure Gitea and Drone * Configure Lohr webhook and SSH key * Configure Jellyfin -* Configure Jackett and NZBHydra2 +* Configure Prowlarr,Jackett and NZBHydra2 * Configure Sonarr, Radarr, Bazarr * Configure Transmission's webui port * Configure Quassel user +* Configure Flood account diff --git a/bootstrap.sh b/bootstrap.sh index a3f9ac9..df41c29 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -58,6 +58,8 @@ get_ssh() { get_doc "SysAdmin/SSH" "shared-key-public" "$HOME/.ssh/shared_rsa.pub" 644 get_doc "SysAdmin/SSH" "shared-key-private" "$HOME/.ssh/shared_rsa" 600 + get_doc "SysAdmin/SSH" "agenix-public" "$HOME/.ssh/id_ed25519.pub" 644 + get_doc "SysAdmin/SSH" "agenix-private" "$HOME/.ssh/id_ed25519" 600 } get_pgp() { @@ -78,7 +80,7 @@ get_pgp() { } get_creds() { - BW_SESSION="$(bw login --raw)" + BW_SESSION="$(bw login --raw || bw unlock --raw)" export BW_SESSION get_ssh diff --git a/flake.lock b/flake.lock index f80cf84..dede124 100644 --- a/flake.lock +++ b/flake.lock @@ -1,12 +1,64 @@ { "nodes": { + "agenix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1662046976, + "narHash": "sha256-BrTReGRhkVm/Kmmf4zQrL+oYWy0sds/BDBgXNX1CL3c=", + "owner": "ryantm", + "repo": "agenix", + "rev": "9f136ecfa5bf954538aed3245e4408cf87c85097", + "type": "github" + }, + "original": { + "owner": "ryantm", + "ref": "main", + "repo": "agenix", + "type": "github" + } + }, + "devshell": { + "locked": { + "lastModified": 1642188268, + "narHash": "sha256-DNz4xScpXIn7rSDohdayBpPR9H9OWCMDOgTYegX081k=", + "owner": "numtide", + "repo": "devshell", + "rev": "696acc29668b644df1740b69e1601119bf6da83b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "devshell", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1641205782, + "narHash": "sha256-4jY7RCWUoZ9cKD8co0/4tFARpWB+57+r1bLLvXNJliY=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "b7547d3eed6f32d06102ead8991ec52ab0a4f1a7", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, "futils": { "locked": { - "lastModified": 1619345332, - "narHash": "sha256-qHnQkEp1uklKTpx3MvKtY6xzgcqXDsz5nLilbbuL+3A=", + "lastModified": 1659877975, + "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=", "owner": "numtide", "repo": "flake-utils", - "rev": "2ebf2558e5bf978c7fb8ea927dfaed8fefab2e28", + "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0", "type": "github" }, "original": { @@ -20,14 +72,17 @@ "inputs": { "nixpkgs": [ "nixpkgs" + ], + "utils": [ + "futils" ] }, "locked": { - "lastModified": 1619558193, - "narHash": "sha256-DljP5/9EX0eXEPhzCUFqFEHkkcFuXJBx1PTgcv0OgyM=", + "lastModified": 1661824092, + "narHash": "sha256-nSWLWytlXbeLrx5A+r5Pso7CvVrX5EgmIIXW/EXvPHQ=", "owner": "nix-community", "repo": "home-manager", - "rev": "18ad12d52b8cebbb57013865eec2be5125de050a", + "rev": "5bd66dc6cd967033489c69d486402b75d338eeb6", "type": "github" }, "original": { @@ -37,13 +92,52 @@ "type": "github" } }, + "matrix-appservices": { + "inputs": { + "devshell": "devshell", + "flake-compat": "flake-compat", + "nixlib": "nixlib", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1662227278, + "narHash": "sha256-n58O7wTmORHg+cwYrbKjnlKi3UQkDug9l0FY9RB6zIM=", + "owner": "coffeetables", + "repo": "nix-matrix-appservices", + "rev": "41c1418781dbdfae5d1c15a29b7f1b8f67e5d69f", + "type": "gitlab" + }, + "original": { + "owner": "coffeetables", + "ref": "main", + "repo": "nix-matrix-appservices", + "type": "gitlab" + } + }, + "nixlib": { + "locked": { + "lastModified": 1643502816, + "narHash": "sha256-Wrbt6Gs+hjXD3HUICPBJHKnHEUqiyx8rzHCgvqC1Bok=", + "owner": "divnix", + "repo": "nixpkgs.lib", + "rev": "ebed7ec5bcb5d01e298535989c6c321df18b631a", + "type": "github" + }, + "original": { + "owner": "divnix", + "repo": "nixpkgs.lib", + "type": "github" + } + }, "nixpkgs": { "locked": { - "lastModified": 1619464443, - "narHash": "sha256-R7WAb8EnkIJxxaF6GTHUPytjonhB4Zm0iatyWoW169A=", + "lastModified": 1662019588, + "narHash": "sha256-oPEjHKGGVbBXqwwL+UjsveJzghWiWV0n9ogo1X6l4cw=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "8e4fe32876ca15e3d5eb3ecd3ca0b224417f5f17", + "rev": "2da64a81275b68fdad38af669afeda43d401e94b", "type": "github" }, "original": { @@ -55,11 +149,11 @@ }, "nur": { "locked": { - "lastModified": 1619628114, - "narHash": "sha256-s3pQyvMfXVmbQOX224yOWQf6zi8406sShFF4u17LVQ0=", + "lastModified": 1662103084, + "narHash": "sha256-zE6ftit1nllgrXJ3hnt/h/Ev+JsjkJQLKAgO5M31R5s=", "owner": "nix-community", "repo": "NUR", - "rev": "0615e756dc14986c4968fa478c0bd080d621cb2b", + "rev": "65fef905eaad9a585a3841103ed3f45608a50c56", "type": "github" }, "original": { @@ -69,12 +163,39 @@ "type": "github" } }, + "pre-commit-hooks": { + "inputs": { + "flake-utils": [ + "futils" + ], + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1660830093, + "narHash": "sha256-HUhx3a82C7bgp2REdGFeHJdhEAzMGCk3V8xIvfBqg1I=", + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "rev": "8cb8ea5f1c7bc2984f460587fddd5f2e558f6eb8", + "type": "github" + }, + "original": { + "owner": "cachix", + "ref": "master", + "repo": "pre-commit-hooks.nix", + "type": "github" + } + }, "root": { "inputs": { + "agenix": "agenix", "futils": "futils", "home-manager": "home-manager", + "matrix-appservices": "matrix-appservices", "nixpkgs": "nixpkgs", - "nur": "nur" + "nur": "nur", + "pre-commit-hooks": "pre-commit-hooks" } } }, diff --git a/flake.nix b/flake.nix index f582719..84f170f 100644 --- a/flake.nix +++ b/flake.nix @@ -1,6 +1,16 @@ { description = "NixOS configuration with flakes"; inputs = { + agenix = { + type = "github"; + owner = "ryantm"; + repo = "agenix"; + ref = "main"; + inputs = { + nixpkgs.follows = "nixpkgs"; + }; + }; + futils = { type = "github"; owner = "numtide"; @@ -15,6 +25,20 @@ ref = "master"; inputs = { nixpkgs.follows = "nixpkgs"; + utils.follows = "futils"; + }; + }; + + matrix-appservices = { + type = "gitlab"; + owner = "coffeetables"; + repo = "nix-matrix-appservices"; + ref = "main"; + inputs = { + # devshell.follows = "devshell"; + # flake-compat.follows = "flake-compat"; + # nixlib.follows = "nixlib"; + nixpkgs.follows = "nixpkgs"; }; }; @@ -31,11 +55,40 @@ repo = "NUR"; ref = "master"; }; + + pre-commit-hooks = { + type = "github"; + owner = "cachix"; + repo = "pre-commit-hooks.nix"; + ref = "master"; + inputs = { + flake-utils.follows = "futils"; + nixpkgs.follows = "nixpkgs"; + }; + }; }; - outputs = { self, futils, home-manager, nixpkgs, nur } @ inputs: + outputs = + inputs @ + { self + , agenix + , futils + , home-manager + , matrix-appservices + , nixpkgs + , nur + , pre-commit-hooks + }: let - inherit (futils.lib) eachDefaultSystem; + inherit (futils.lib) eachSystem system; + + mySystems = [ + system.aarch64-linux + system.x86_64-darwin + system.x86_64-linux + ]; + + eachMySystem = eachSystem mySystems; lib = nixpkgs.lib.extend (self: super: { my = import ./lib { inherit inputs; pkgs = nixpkgs; lib = self; }; @@ -51,58 +104,83 @@ nur.overlay ]; } - home-manager.nixosModules.home-manager - { - home-manager.users.ambroisie = import ./home; - # Nix Flakes compatibility - home-manager.useGlobalPkgs = true; - home-manager.useUserPackages = true; - } + # Include generic settings + ./modules + # Include bundles of settings + ./profiles ]; buildHost = name: system: lib.nixosSystem { inherit system; modules = defaultModules ++ [ - (./. + "/${name}.nix") + (./. + "/machines/${name}") ]; specialArgs = { # Use my extended lib in NixOS configuration inherit lib; + # Inject inputs to use them in global registry + inherit inputs; }; }; in - eachDefaultSystem + eachMySystem (system: - let - pkgs = nixpkgs.legacyPackages.${system}; - in - rec { - apps = { - diff-flake = futils.lib.mkApp { drv = packages.diff-flake; }; + let + pkgs = nixpkgs.legacyPackages.${system}; + in + rec { + apps = { + diff-flake = futils.lib.mkApp { drv = packages.diff-flake; }; + default = apps.diff-flake; + }; + + checks = { + pre-commit = pre-commit-hooks.lib.${system}.run { + src = ./.; + + hooks = { + nixpkgs-fmt = { + enable = true; + }; + + shellcheck = { + enable = true; + }; + }; }; + }; - defaultApp = apps.diff-flake; - - devShell = pkgs.mkShell { + devShells = { + default = pkgs.mkShell { name = "NixOS-config"; - buildInputs = with pkgs; [ - git-crypt + + nativeBuildInputs = with pkgs; [ gitAndTools.pre-commit - gnupg nixpkgs-fmt ]; + + inherit (self.checks.${system}.pre-commit) shellHook; }; + }; - packages = import ./pkgs { inherit pkgs; }; - }) // { - overlay = self.overlays.pkgs; - - overlays = { + packages = + let + inherit (futils.lib) filterPackages flattenTree; + packages = import ./pkgs { inherit pkgs; }; + flattenedPackages = flattenTree packages; + finalPackages = filterPackages system flattenedPackages; + in + finalPackages; + }) // { + overlays = import ./overlays // { lib = final: prev: { inherit lib; }; - pkgs = final: prev: { ambroisie = import ./pkgs { pkgs = prev; }; }; + pkgs = final: prev: { + ambroisie = prev.recurseIntoAttrs (import ./pkgs { pkgs = prev; }); + }; }; nixosConfigurations = lib.mapAttrs buildHost { + aramis = "x86_64-linux"; porthos = "x86_64-linux"; }; }; diff --git a/home/bat.nix b/home/bat/default.nix similarity index 91% rename from home/bat.nix rename to home/bat/default.nix index 8485dd3..ac58c06 100644 --- a/home/bat.nix +++ b/home/bat/default.nix @@ -10,6 +10,8 @@ in config.programs.bat = lib.mkIf cfg.enable { enable = true; config = { + theme = "gruvbox-dark"; + pager = with config.home.sessionVariables; "${PAGER} ${LESS}"; }; }; diff --git a/home/bluetooth/default.nix b/home/bluetooth/default.nix new file mode 100644 index 0000000..2a4f613 --- /dev/null +++ b/home/bluetooth/default.nix @@ -0,0 +1,19 @@ +{ config, lib, ... }: +let + cfg = config.my.home.bluetooth; +in +{ + options.my.home.bluetooth = with lib; { + enable = mkEnableOption "bluetooth configuration"; + }; + + config = lib.mkIf cfg.enable { + services.blueman-applet = { + enable = true; + }; + + services.mpris-proxy = { + enable = true; + }; + }; +} diff --git a/home/comma/default.nix b/home/comma/default.nix new file mode 100644 index 0000000..cc6a0ad --- /dev/null +++ b/home/comma/default.nix @@ -0,0 +1,29 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.home.comma; +in +{ + options.my.home.comma = with lib; { + enable = my.mkDisableOption "comma configuration"; + + pkgsFlake = mkOption { + type = types.str; + default = "pkgs"; + example = "nixpkgs"; + description = '' + Which flake from the registry should be used with + nix shell. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + home.packages = with pkgs; [ + ambroisie.comma + ]; + + home.sessionVariables = { + COMMA_PKGS_FLAKE = cfg.pkgsFlake; + }; + }; +} diff --git a/home/default.nix b/home/default.nix index e8218e8..82d2cc3 100644 --- a/home/default.nix +++ b/home/default.nix @@ -1,20 +1,38 @@ { ... }: { imports = [ - ./bat.nix - ./direnv.nix - ./documentation.nix + ./bat + ./bluetooth + ./comma + ./direnv + ./discord + ./documentation + ./feh + ./firefox + ./flameshot + ./gammastep + ./gdb ./git - ./gpg.nix - ./htop.nix - ./jq.nix - ./packages.nix - ./pager.nix - ./secrets # Home-manager specific secrets - ./ssh.nix - ./tmux.nix + ./gpg + ./gtk + ./htop + ./jq + ./mail + ./mpv + ./nix-index + ./nm-applet + ./packages + ./pager + ./power-alert + ./ssh + ./terminal + ./tmux + ./udiskie ./vim - ./xdg.nix + ./wm + ./x + ./xdg + ./zathura ./zsh ]; @@ -23,4 +41,7 @@ # Who am I? home.username = "ambroisie"; + + # Start services automatically + systemd.user.startServices = "sd-switch"; } diff --git a/home/direnv.nix b/home/direnv.nix deleted file mode 100644 index d81cf46..0000000 --- a/home/direnv.nix +++ /dev/null @@ -1,15 +0,0 @@ -{ config, lib, ... }: -let - cfg = config.my.home.direnv; -in -{ - options.my.home.direnv = with lib.my; { - enable = mkDisableOption "direnv configuration"; - }; - - config.programs.direnv = lib.mkIf cfg.enable { - enable = true; - # A better `use_nix` - enableNixDirenvIntegration = true; - }; -} diff --git a/home/direnv/default.nix b/home/direnv/default.nix new file mode 100644 index 0000000..93a1f3b --- /dev/null +++ b/home/direnv/default.nix @@ -0,0 +1,46 @@ +{ config, lib, ... }: +let + cfg = config.my.home.direnv; +in +{ + options.my.home.direnv = with lib; { + enable = my.mkDisableOption "direnv configuration"; + + defaultFlake = mkOption { + type = types.str; + default = "pkgs"; + example = "nixpkgs"; + description = '' + Which flake from the registry should be used for + use pkgs by default. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + programs.direnv = { + enable = true; + nix-direnv = { + # A better `use_nix` + enable = true; + }; + }; + + xdg.configFile = + let + libDir = ./lib; + contents = builtins.readDir libDir; + names = lib.attrNames contents; + files = lib.filter (name: contents.${name} == "regular") names; + linkLibFile = name: + lib.nameValuePair + "direnv/lib/${name}" + { source = libDir + "/${name}"; }; + in + lib.my.genAttrs' files linkLibFile; + + home.sessionVariables = { + DIRENV_DEFAULT_FLAKE = cfg.defaultFlake; + }; + }; +} diff --git a/home/direnv/lib/nix.sh b/home/direnv/lib/nix.sh new file mode 100644 index 0000000..2d40b20 --- /dev/null +++ b/home/direnv/lib/nix.sh @@ -0,0 +1,32 @@ +#shellcheck shell=bash + +use_pkgs() { + if ! has nix; then + # shellcheck disable=2016 + log_error 'use_pkgs: `nix` is not in PATH' + return 1 + fi + + # Use user-provided default value, or fallback to nixpkgs + local DEFAULT_FLAKE="${DIRENV_DEFAULT_FLAKE:-nixpkgs}" + + # Allow changing the default flake through a command line switch + if [ "$1" = "-f" ] || [ "$1" = "--flake" ]; then + DEFAULT_FLAKE="$2" + shift 2 + fi + + + # Allow specifying a full installable, or just a package name and use the default flake + local packages=() + for pkg; do + if [[ $pkg =~ .*#.* ]]; then + packages+=("$pkg") + else + packages+=("$DEFAULT_FLAKE#$pkg") + fi + done + + # shellcheck disable=2154 + direnv_load nix shell "${packages[@]}" --command "$direnv" dump +} diff --git a/home/direnv/lib/postgres.sh b/home/direnv/lib/postgres.sh new file mode 100644 index 0000000..c2e6a8f --- /dev/null +++ b/home/direnv/lib/postgres.sh @@ -0,0 +1,22 @@ +#shellcheck shell=bash + +layout_postgres() { + if ! has postgres || ! has initdb; then + # shellcheck disable=2016 + log_error 'layout_postgres: `postgres` and `initdb` are not in PATH' + return 1 + fi + + # shellcheck disable=2155 + export PGDATA="$(direnv_layout_dir)/postgres" + export PGHOST="$PGDATA" + + if [[ ! -d "$PGDATA" ]]; then + initdb + cat >> "$PGDATA/postgresql.conf" << EOF +listen_addresses = '' +unix_socket_directories = '$PGHOST' +EOF + echo "CREATE DATABASE $USER;" | postgres --single -E postgres + fi +} diff --git a/home/direnv/lib/python.sh b/home/direnv/lib/python.sh new file mode 100644 index 0000000..15a273f --- /dev/null +++ b/home/direnv/lib/python.sh @@ -0,0 +1,25 @@ +#shellcheck shell=bash + +layout_poetry() { + if ! has poetry; then + # shellcheck disable=2016 + log_error 'layout_poetry: `poetry` is not in PATH' + return 1 + fi + + if [[ ! -f pyproject.toml ]]; then + # shellcheck disable=2016 + log_error 'layout_poetry: no pyproject.toml found. Use `poetry new` or `poetry init` to create one first' + return 1 + fi + + # create venv if it doesn't exist + poetry run true + + # shellcheck disable=2155 + export VIRTUAL_ENV=$(poetry env info --path) + export POETRY_ACTIVE=1 + PATH_add "$VIRTUAL_ENV/bin" + watch_file pyproject.toml + watch_file poetry.lock +} diff --git a/home/discord/default.nix b/home/discord/default.nix new file mode 100644 index 0000000..7348bb4 --- /dev/null +++ b/home/discord/default.nix @@ -0,0 +1,23 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.home.discord; + + jsonFormat = pkgs.formats.json { }; +in +{ + options.my.home.discord = with lib; { + enable = mkEnableOption "discord configuration"; + }; + + config = lib.mkIf cfg.enable { + home.packages = with pkgs; [ + discord + ]; + + xdg.configFile."discord/settings.json".source = + jsonFormat.generate "discord.json" { + # Do not keep me from using the app just to force an update + SKIP_HOST_UPDATE = true; + }; + }; +} diff --git a/home/documentation.nix b/home/documentation/default.nix similarity index 100% rename from home/documentation.nix rename to home/documentation/default.nix diff --git a/home/feh/default.nix b/home/feh/default.nix new file mode 100644 index 0000000..3a952a2 --- /dev/null +++ b/home/feh/default.nix @@ -0,0 +1,13 @@ +{ config, lib, ... }: +let + cfg = config.my.home.feh; +in +{ + options.my.home.feh = with lib; { + enable = mkEnableOption "feh configuration"; + }; + + config.programs.feh = lib.mkIf cfg.enable { + enable = true; + }; +} diff --git a/home/firefox/default.nix b/home/firefox/default.nix new file mode 100644 index 0000000..41389b5 --- /dev/null +++ b/home/firefox/default.nix @@ -0,0 +1,84 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.home.firefox; +in +{ + imports = [ + ./tridactyl + ]; + + options.my.home.firefox = with lib; { + enable = mkEnableOption "firefox configuration"; + + tridactyl = { + enable = mkOption { + type = types.bool; + description = "tridactyl configuration"; + example = false; + default = config.my.home.firefox.enable; + }; + }; + + ff2mpv = { + enable = mkOption { + type = types.bool; + description = "ff2mpv configuration"; + example = false; + default = config.my.home.mpv.enable; + }; + }; + }; + + config.programs.firefox = lib.mkIf cfg.enable { + enable = true; + + package = pkgs.firefox.override { + cfg = { + enableTridactylNative = cfg.tridactyl.enable; + }; + + extraNativeMessagingHosts = with pkgs; ([ ] + # Watch videos using mpv + ++ lib.optional cfg.ff2mpv.enable ambroisie.ff2mpv-go + ); + }; + + profiles = { + default = { + id = 0; + + settings = { + "browser.bookmarks.showMobileBookmarks" = true; # Mobile bookmarks + "browser.download.useDownloadDir" = false; # Ask for download location + "browser.in-content.dark-mode" = true; # Dark mode + "browser.newtabpage.activity-stream.feeds.section.topstories" = false; # Disable top stories + "browser.newtabpage.activity-stream.feeds.sections" = false; + "browser.newtabpage.activity-stream.feeds.system.topstories" = false; # Disable top stories + "browser.newtabpage.activity-stream.section.highlights.includePocket" = false; # Disable pocket + "extensions.pocket.enabled" = false; # Disable pocket + "media.eme.enabled" = true; # Enable DRM + "media.gmp-widevinecdm.visible" = true; # Enable DRM + "media.gmp-widevinecdm.enabled" = true; # Enable DRM + "signon.autofillForms" = false; # Disable built-in form-filling + "signon.rememberSignons" = false; # Disable built-in password manager + "ui.systemUsesDarkTheme" = true; # Dark mode + }; + }; + }; + + extensions = with pkgs.nur.repos.rycee.firefox-addons; ([ + bitwarden + consent-o-matic + form-history-control + https-everywhere + reddit-comment-collapser + reddit-enhancement-suite + refined-github + sponsorblock + ublock-origin + ] + ++ lib.optional (cfg.tridactyl.enable) tridactyl + ++ lib.optional (cfg.ff2mpv.enable) ff2mpv + ); + }; +} diff --git a/home/firefox/tridactyl/default.nix b/home/firefox/tridactyl/default.nix new file mode 100644 index 0000000..fd8e3fb --- /dev/null +++ b/home/firefox/tridactyl/default.nix @@ -0,0 +1,9 @@ +{ config, lib, ... }: +let + cfg = config.my.home.firefox.tridactyl; +in +{ + config = lib.mkIf cfg.enable { + xdg.configFile."tridactyl/tridactylrc".source = ./tridactylrc; + }; +} diff --git a/home/firefox/tridactyl/tridactylrc b/home/firefox/tridactyl/tridactylrc new file mode 100644 index 0000000..711bf6f --- /dev/null +++ b/home/firefox/tridactyl/tridactylrc @@ -0,0 +1,78 @@ +" Shamelessly taken from bovine3dom's example configuration file from the docs + +" Basics {{{ +" Use dark color scheme +colorscheme dark + +" Make tridactyl open Vim in my prefered terminal +" FIXME: make it follow my prefered terminal +set editorcmd termite --class tridactyl_editor -e 'vim %f' +" }}} + +" Binds {{{ +" Reddit et al. {{{ +" Toggle comments on Reddit, Hacker News, Lobste.rs +bind ;c hint -Jc [class*="expand"],[class*="togg"],[class="comment_folder"] + +" Make `gu` take me back to subreddit from comments +bindurl reddit.com gu urlparent 3 + +" Only hint search results on Google +bindurl www.google.com f hint -Jc #search div:not(.action-menu) > a +bindurl www.google.com F hint -Jbc #search div:not(.action-menu) > a + +" Only hint search results on DuckDuckGo +bindurl ^https://duckduckgo.com f hint -Jc [class~=result__a] +bindurl ^https://duckduckgo.com F hint -Jbc [class~=result__a] + +" Only hint item pages on Hacker News +bindurl news.ycombinator.com ;f hint -Jc .age > a +bindurl news.ycombinator.com ;F hint -Jtc .age > a +" }}} + +" Better bindings {{{ +" Handy multiwindow binds +bind gd tabdetach +bind gD composite tabduplicate; tabdetach + +" Duplicate a tab without detaching window +bind d tabduplicate + +" Make yy use canonical links on the few websites that support them +bind yy clipboard yankcanon +" }}} + +" Search {{{ +" Case insensitive only if fully lowercase +set findcase smart + +" Search forward/backward +bind / fillcmdline find +bind ? fillcmdline find -? + +" Go to next/previous match +bind n findnext 1 +bind N findnext -1 + +" Because :nohls never works +bind nohlsearch + +" Use browser's native find when using Ctrl-F +unbind +" }}} +" }}} + +" Redirections {{{ +" Always redirect Reddit to the old site +autocmd DocStart ^http(s?)://www.reddit.com js tri.excmds.urlmodify("-t", "www", "old") +" Use a better Twitter front-end +autocmd DocStart ^http(s?)://twitter.com js tri.excmds.urlmodify("-t", "twitter.com", "nitter.net") +" }}} + +" Disabled websites {{{ +blacklistadd netflix.com +blacklistadd primevideo.com +blacklistadd jellyfin.belanyi.fr +" }}} + +" vim: set filetype=vim foldmethod=marker: diff --git a/home/flameshot/default.nix b/home/flameshot/default.nix new file mode 100644 index 0000000..a9a60a8 --- /dev/null +++ b/home/flameshot/default.nix @@ -0,0 +1,13 @@ +{ config, lib, ... }: +let + cfg = config.my.home.flameshot; +in +{ + options.my.home.flameshot = with lib; { + enable = mkEnableOption "flameshot configuration"; + }; + + config.services.flameshot = lib.mkIf cfg.enable { + enable = true; + }; +} diff --git a/home/gammastep/default.nix b/home/gammastep/default.nix new file mode 100644 index 0000000..3e20094 --- /dev/null +++ b/home/gammastep/default.nix @@ -0,0 +1,44 @@ +{ config, lib, ... }: +let + cfg = config.my.home.gammastep; + + mkTempOption = with lib; description: default: mkOption { + inherit description default; + type = types.int; + example = 1000; + }; + + mkTimeOption = with lib; description: default: mkOption { + inherit description default; + type = types.str; + example = "12:00-14:00"; + }; +in +{ + options.my.home.gammastep = with lib; { + enable = mkEnableOption "gammastep configuration"; + + temperature = { + day = mkTempOption "Colour temperature to use during the day" 6500; + night = mkTempOption "Colour temperature to use during the night" 2000; + }; + + times = { + dawn = mkTimeOption "Dawn time" "6:00-7:30"; + dusk = mkTimeOption "Dusk time" "18:30-20:00"; + }; + }; + + config.services.gammastep = lib.mkIf cfg.enable { + enable = true; + + tray = true; + + dawnTime = cfg.times.dawn; + duskTime = cfg.times.dusk; + + temperature = { + inherit (cfg.temperature) day night; + }; + }; +} diff --git a/home/gdb/default.nix b/home/gdb/default.nix new file mode 100644 index 0000000..c498048 --- /dev/null +++ b/home/gdb/default.nix @@ -0,0 +1,38 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.home.gdb; +in +{ + options.my.home.gdb = with lib; { + enable = my.mkDisableOption "gdb configuration"; + + rr = { + enable = my.mkDisableOption "rr configuration"; + + package = mkOption { + type = types.package; + default = pkgs.rr; + defaultText = literalExample "pkgs.rr"; + description = '' + Package providing rr + ''; + }; + }; + }; + + config = lib.mkMerge [ + (lib.mkIf cfg.enable { + home.packages = with pkgs; [ + gdb + ]; + + xdg.configFile."gdb/gdbinit".source = ./gdbinit; + }) + + (lib.mkIf cfg.rr.enable { + home.packages = [ + cfg.rr.package + ]; + }) + ]; +} diff --git a/home/gdb/gdbinit b/home/gdb/gdbinit new file mode 100644 index 0000000..86e8c3c --- /dev/null +++ b/home/gdb/gdbinit @@ -0,0 +1,24 @@ + # Keep a history of all commands in each directory +set history save on + +# Enable those pretty-printers +enable pretty-printer + +# Pretty formatting of structures +set print pretty on +# Show derived type based on VTable +set print object on +# Show static members +set print static-members on +# Show VTable +set print vtbl on +# Demangle types +set print demangle on + +# Read python scrips in the load path +set auto-load python-scripts + +# Allow autoloading project-local .gdbinit files +add-auto-load-safe-path ~/git/ +# Allow autoloading from the Nix store +add-auto-load-safe-path /nix/store diff --git a/home/git/default.ignore b/home/git/default.ignore index a169a28..77aedcc 100644 --- a/home/git/default.ignore +++ b/home/git/default.ignore @@ -24,3 +24,7 @@ compile_commands.json # Swap and backup files *~ ~.swp + +# Direnv files +.envrc +.direnv/ diff --git a/home/git/default.nix b/home/git/default.nix index da5efae..9f59d52 100644 --- a/home/git/default.nix +++ b/home/git/default.nix @@ -1,34 +1,72 @@ { config, pkgs, lib, ... }: let cfg = config.my.home.git; + + inherit (lib.my) mkMailAddress; in { options.my.home.git = with lib.my; { enable = mkDisableOption "git configuration"; }; + config.home.packages = with pkgs.gitAndTools; lib.mkIf cfg.enable [ + gitAndTools.git-absorb + gitAndTools.git-revise + gitAndTools.tig + ]; + config.programs.git = lib.mkIf cfg.enable { enable = true; # Who am I? - userEmail = "bruno@belanyi.fr"; + userEmail = mkMailAddress "bruno" "belanyi.fr"; userName = "Bruno BELANYI"; # I want the full experience package = pkgs.gitAndTools.gitFull; aliases = { - lol = "log --graph --decorate --pretty=oneline --abbrev-commit"; + git = "!git"; + lol = "log --graph --decorate --pretty=oneline --abbrev-commit --topo-order"; lola = "lol --all"; assume = "update-index --assume-unchanged"; unassume = "update-index --no-assume-unchanged"; assumed = "!git ls-files -v | grep ^h | cut -c 3-"; + pick = "log -p -G"; push-new = "!git push -u origin " + ''"$(git branch | grep '^* ' | cut -f2- -d' ')"''; }; lfs.enable = true; + delta = { + enable = true; + + options = { + features = "diff-highlight decorations"; + + # Less jarring style for `diff-highlight` emulation + diff-highlight = { + minus-style = "red"; + minus-non-emph-style = "red"; + minus-emph-style = "bold red 52"; + + plus-style = "green"; + plus-non-emph-style = "green"; + plus-emph-style = "bold green 22"; + + whitespace-error-style = "reverse red"; + }; + + # Personal preference for easier reading + decorations = { + commit-style = "raw"; # Do not recolor meta information + keep-plus-minus-markers = true; + paging = "always"; + }; + }; + }; + # There's more extraConfig = { # Makes it a bit more readable @@ -62,13 +100,6 @@ in whitespace = "red reverse"; }; - "color.diff-highlight" = { - oldNormal = "red bold"; - oldHighlight = "red bold 52"; - newNormal = "green bold"; - newHighlight = "green bold 22"; - }; - commit = { # Show my changes when writing the message verbose = true; @@ -89,15 +120,9 @@ in defaultBranch = "main"; }; - pager = - let - diff-highlight = "${pkgs.gitAndTools.gitFull}/share/git/contrib/diff-highlight/diff-highlight"; - in - { - diff = "${diff-highlight} | less"; - log = "${diff-highlight} | less"; - show = "${diff-highlight} | less"; - }; + merge = { + conflictStyle = "zdiff3"; + }; pull = { # Avoid useless merge commits @@ -114,11 +139,33 @@ in autoSquash = true; autoStash = true; }; + + url = { + "git@gitea.belanyi.fr:" = { + insteadOf = "https://gitea.belanyi.fr/"; + }; + + "git@github.com:" = { + insteadOf = "https://github.com/"; + }; + + "git@gitlab.com:" = { + insteadOf = "https://gitlab.com/"; + }; + }; }; # Multiple identities includes = [ - { path = ./epita.config; condition = "gitdir:~/git/EPITA/"; } + { + condition = "gitdir:~/git/EPITA/"; + contents = { + user = { + name = "Bruno BELANYI"; + email = mkMailAddress "bruno.belanyi" "epita.fr"; + }; + }; + } ]; ignores = diff --git a/home/git/epita.config b/home/git/epita.config deleted file mode 100644 index a6e8cf4..0000000 --- a/home/git/epita.config +++ /dev/null @@ -1,4 +0,0 @@ -[user] - email = bruno.belanyi@epita.fr - name = Bruno BELANYI -# vim: set ft=gitconfig: diff --git a/home/gpg.nix b/home/gpg/default.nix similarity index 53% rename from home/gpg.nix rename to home/gpg/default.nix index 548f90b..d48c200 100644 --- a/home/gpg.nix +++ b/home/gpg/default.nix @@ -3,8 +3,15 @@ let cfg = config.my.home.gpg; in { - options.my.home.gpg = with lib.my; { - enable = mkDisableOption "gpg configuration"; + options.my.home.gpg = with lib; { + enable = my.mkDisableOption "gpg configuration"; + + pinentry = mkOption { + type = types.str; + default = "tty"; + example = "gtk2"; + description = "Which pinentry interface to use"; + }; }; config = lib.mkIf cfg.enable { @@ -15,7 +22,7 @@ in services.gpg-agent = { enable = true; enableSshSupport = true; # One agent to rule them all - pinentryFlavor = "tty"; + pinentryFlavor = cfg.pinentry; extraConfig = '' allow-loopback-pinentry ''; diff --git a/home/gtk/default.nix b/home/gtk/default.nix new file mode 100644 index 0000000..62d3f81 --- /dev/null +++ b/home/gtk/default.nix @@ -0,0 +1,33 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.home.gtk; +in +{ + options.my.home.gtk = with lib; { + enable = mkEnableOption "GTK configuration"; + }; + + config.gtk = lib.mkIf cfg.enable { + enable = true; + + font = { + package = pkgs.dejavu_fonts; + name = "DejaVu Sans"; + }; + + gtk2 = { + # That sweet, sweet clean home that I am always aiming for... + configLocation = "${config.xdg.configHome}/gtk-2.0/gtkrc"; + }; + + iconTheme = { + package = pkgs.gnome.gnome-themes-extra; + name = "Adwaita"; + }; + + theme = { + package = pkgs.gnome.gnome-themes-extra; + name = "Adwaita"; + }; + }; +} diff --git a/home/htop.nix b/home/htop/default.nix similarity index 100% rename from home/htop.nix rename to home/htop/default.nix diff --git a/home/jq.nix b/home/jq/default.nix similarity index 100% rename from home/jq.nix rename to home/jq/default.nix diff --git a/home/mail/accounts/default.nix b/home/mail/accounts/default.nix new file mode 100644 index 0000000..ee8ec46 --- /dev/null +++ b/home/mail/accounts/default.nix @@ -0,0 +1,91 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.home.mail; + + inherit (lib.my) mkMailAddress; + + mkConfig = { domain, address, passName, aliases ? [ ], primary ? false }: { + realName = lib.mkDefault "Bruno BELANYI"; + userName = lib.mkDefault (mkMailAddress address domain); + passwordCommand = + lib.mkDefault [ "${pkgs.ambroisie.bw-pass}/bin/bw-pass" "Mail" passName ]; + + address = mkMailAddress address domain; + aliases = builtins.map (lib.flip mkMailAddress domain) aliases; + + inherit primary; + + himalaya = { + enable = cfg.himalaya.enable; + }; + + msmtp = { + enable = cfg.msmtp.enable; + }; + }; + + migaduConfig = { + imap = { + host = "imap.migadu.com"; + port = 993; + tls = { + enable = true; + }; + }; + smtp = { + host = "smtp.migadu.com"; + port = 465; + tls = { + enable = true; + }; + }; + }; + + gmailConfig = { + flavor = "gmail.com"; + folders = { + drafts = "[Gmail]/Drafts"; + sent = "[Gmail]/Sent Mail"; + trash = "[Gmail]/Trash"; + }; + }; + + office365Config = { + flavor = "outlook.office365.com"; + }; +in +{ + config.accounts.email.accounts = { + personal = lib.mkMerge [ + # Common configuraton + (mkConfig { + domain = "belanyi.fr"; + address = "bruno"; + passName = "Migadu"; + aliases = [ "admin" "postmaster" ]; + primary = true; # This is my primary email + }) + migaduConfig + ]; + + gmail = lib.mkMerge [ + # Common configuraton + (mkConfig { + domain = "gmail.com"; + address = "brunobelanyi"; + passName = "GMail"; + }) + gmailConfig + ]; + + epita = lib.mkMerge [ + # Common configuration + (mkConfig { + domain = "epita.fr"; + address = "bruno.belanyi"; + passName = "EPITA"; + }) + office365Config + ]; + }; +} diff --git a/home/mail/default.nix b/home/mail/default.nix new file mode 100644 index 0000000..ac44593 --- /dev/null +++ b/home/mail/default.nix @@ -0,0 +1,31 @@ +{ config, lib, ... }: +let + cfg = config.my.home.mail; + + mkRelatedOption = desc: lib.mkEnableOption desc // { default = cfg.enable; }; +in +{ + imports = [ + ./accounts + ./himalaya + ./msmtp + ]; + + options.my.home.mail = with lib; { + enable = my.mkDisableOption "email configuration"; + + himalaya = { + enable = mkRelatedOption "himalaya configuration"; + }; + + msmtp = { + enable = mkRelatedOption "msmtp configuration"; + }; + }; + + config = { + accounts.email = { + maildirBasePath = "mail"; + }; + }; +} diff --git a/home/mail/himalaya/default.nix b/home/mail/himalaya/default.nix new file mode 100644 index 0000000..c2d3b05 --- /dev/null +++ b/home/mail/himalaya/default.nix @@ -0,0 +1,23 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.home.mail.himalaya; +in +{ + config.programs.himalaya = lib.mkIf cfg.enable { + enable = true; + + settings = { + notify-cmd = + let + notify-send = "${pkgs.libnotify}/bin/notify-send"; + in + pkgs.writeScript "mail-notifier" '' + SENDER="$1" + SUBJECT="$2" + ${notify-send} \ + -c himalaya \ + -- "$(printf 'Received email from %s\n\n%s' "$SENDER" "$SUBJECT")" + ''; + }; + }; +} diff --git a/home/mail/msmtp/default.nix b/home/mail/msmtp/default.nix new file mode 100644 index 0000000..c469982 --- /dev/null +++ b/home/mail/msmtp/default.nix @@ -0,0 +1,9 @@ +{ config, lib, ... }: +let + cfg = config.my.home.mail.msmtp; +in +{ + config.programs.msmtp = lib.mkIf cfg.enable { + enable = true; + }; +} diff --git a/home/mpv/default.nix b/home/mpv/default.nix new file mode 100644 index 0000000..9aef379 --- /dev/null +++ b/home/mpv/default.nix @@ -0,0 +1,19 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.home.mpv; +in +{ + options.my.home.mpv = with lib; { + enable = mkEnableOption "mpv configuration"; + }; + + config = lib.mkIf cfg.enable { + programs.mpv = { + enable = true; + + scripts = [ + pkgs.mpvScripts.mpris # Allow controlling using media keys + ]; + }; + }; +} diff --git a/home/nix-index/default.nix b/home/nix-index/default.nix new file mode 100644 index 0000000..ae6f338 --- /dev/null +++ b/home/nix-index/default.nix @@ -0,0 +1,13 @@ +{ config, lib, ... }: +let + cfg = config.my.home.nix-index; +in +{ + options.my.home.nix-index = with lib.my; { + enable = mkDisableOption "nix-index configuration"; + }; + + config.programs.nix-index = lib.mkIf cfg.enable { + enable = true; + }; +} diff --git a/home/nm-applet/default.nix b/home/nm-applet/default.nix new file mode 100644 index 0000000..b8637f7 --- /dev/null +++ b/home/nm-applet/default.nix @@ -0,0 +1,13 @@ +{ config, lib, ... }: +let + cfg = config.my.home.nm-applet; +in +{ + options.my.home.nm-applet = with lib; { + enable = mkEnableOption "network-manager-applet configuration"; + }; + + config.services.network-manager-applet = lib.mkIf cfg.enable { + enable = true; + }; +} diff --git a/home/packages.nix b/home/packages.nix deleted file mode 100644 index b951f9e..0000000 --- a/home/packages.nix +++ /dev/null @@ -1,20 +0,0 @@ -{ config, lib, pkgs, ... }: -let - cfg = config.my.home.packages; -in -{ - options.my.home.packages = with lib.my; { - enable = mkDisableOption "user packages"; - }; - - config.home.packages = with pkgs; lib.mkIf cfg.enable [ - # Git related - gitAndTools.git-absorb - gitAndTools.git-revise - gitAndTools.tig - # Dev work - rr - # Terminal prettiness - termite.terminfo - ]; -} diff --git a/home/packages/default.nix b/home/packages/default.nix new file mode 100644 index 0000000..84c1253 --- /dev/null +++ b/home/packages/default.nix @@ -0,0 +1,27 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.home.packages; +in +{ + options.my.home.packages = with lib; { + enable = my.mkDisableOption "user packages"; + + additionalPackages = mkOption { + type = with types; listOf package; + default = [ ]; + example = literalExample '' + with pkgs; [ + quasselClient + ] + ''; + }; + }; + + config.home.packages = with pkgs; lib.mkIf cfg.enable ([ + fd + file + mosh + ripgrep + termite.terminfo + ] ++ cfg.additionalPackages); +} diff --git a/home/pager.nix b/home/pager.nix deleted file mode 100644 index 54ea3c4..0000000 --- a/home/pager.nix +++ /dev/null @@ -1,18 +0,0 @@ -{ config, lib, ... }: -let - cfg = config.my.home.pager; -in -{ - options.my.home.pager = with lib.my; { - enable = mkDisableOption "pager configuration"; - }; - - config.programs.lesspipe.enable = cfg.enable; - - config.home.sessionVariables = lib.mkIf cfg.enable { - # My default pager - PAGER = "less"; - # Clear the screen on start and exit - LESS = "-R -+X -c"; - }; -} diff --git a/home/pager/default.nix b/home/pager/default.nix new file mode 100644 index 0000000..00d29c0 --- /dev/null +++ b/home/pager/default.nix @@ -0,0 +1,36 @@ +{ config, lib, ... }: +let + cfg = config.my.home.pager; +in +{ + options.my.home.pager = with lib.my; { + enable = mkDisableOption "pager configuration"; + }; + + + config = lib.mkIf cfg.enable { + home.sessionVariables = { + # My default pager + PAGER = "less"; + # Clear the screen on start and exit + LESS = "-R -+X -c"; + }; + + programs.zsh.localVariables = { + # Colored man pages + LESS_TERMCAP_mb = "$(tput bold; tput setaf 2)"; + LESS_TERMCAP_md = "$(tput bold; tput setaf 6)"; + LESS_TERMCAP_me = "$(tput sgr0)"; + LESS_TERMCAP_so = "$(tput bold; tput setaf 3; tput setab 4)"; + LESS_TERMCAP_se = "$(tput rmso; tput sgr0)"; + LESS_TERMCAP_us = "$(tput bold; tput setaf 2)"; + LESS_TERMCAP_ue = "$(tput rmul; tput sgr0)"; + LESS_TERMCAP_mr = "$(tput rev)"; + LESS_TERMCAP_mh = "$(tput dim)"; + LESS_TERMCAP_ZN = "$(tput ssubm)"; + LESS_TERMCAP_ZV = "$(tput rsubm)"; + LESS_TERMCAP_ZO = "$(tput ssupm)"; + LESS_TERMCAP_ZW = "$(tput rsupm)"; + }; + }; +} diff --git a/home/power-alert/default.nix b/home/power-alert/default.nix new file mode 100644 index 0000000..8dbb5e6 --- /dev/null +++ b/home/power-alert/default.nix @@ -0,0 +1,15 @@ +{ config, lib, ... }: +let + cfg = config.my.home.power-alert; +in +{ + options.my.home.power-alert = with lib; { + enable = mkEnableOption "power-alert configuration"; + }; + + config = lib.mkIf cfg.enable { + services.poweralertd = { + enable = true; + }; + }; +} diff --git a/home/secrets/.gitattributes b/home/secrets/.gitattributes deleted file mode 100644 index a741d4d..0000000 --- a/home/secrets/.gitattributes +++ /dev/null @@ -1,3 +0,0 @@ -* filter=git-crypt diff=git-crypt -.gitattributes !filter !diff -/default.nix !filter !diff diff --git a/home/secrets/canary b/home/secrets/canary deleted file mode 100644 index e910ea3..0000000 Binary files a/home/secrets/canary and /dev/null differ diff --git a/home/secrets/default.nix b/home/secrets/default.nix deleted file mode 100644 index 3624472..0000000 --- a/home/secrets/default.nix +++ /dev/null @@ -1,19 +0,0 @@ -{ lib, ... }: - -with lib; -let - canaryHash = builtins.hashFile "sha256" ./canary; - expectedHash = - "9df8c065663197b5a1095122d48e140d3677d860343256abd5ab6e4fb4c696ab"; -in -if canaryHash != expectedHash then - abort "Secrets are not readable. Have you run `git-crypt unlock`?" -else { - options.my.secrets = mkOption { - type = types.attrs; - }; - - config.my.secrets = { - # Home-manager secrets go here - }; -} diff --git a/home/ssh.nix b/home/ssh/default.nix similarity index 83% rename from home/ssh.nix rename to home/ssh/default.nix index 22f85d3..cbfd30c 100644 --- a/home/ssh.nix +++ b/home/ssh/default.nix @@ -7,7 +7,7 @@ in enable = mkDisableOption "ssh configuration"; }; - config.programs.ssh = { + config.programs.ssh = lib.mkIf cfg.enable { enable = true; matchBlocks = { @@ -40,6 +40,12 @@ in identityFile = "~/.ssh/shared_rsa"; user = "ambroisie"; }; + + work = { + hostname = "workspaces.dgexsol.fr"; + identityFile = "~/.ssh/shared_rsa"; + user = "bruno_belanyi"; + }; }; extraConfig = '' diff --git a/home/terminal/default.nix b/home/terminal/default.nix new file mode 100644 index 0000000..68ff44e --- /dev/null +++ b/home/terminal/default.nix @@ -0,0 +1,61 @@ +{ config, lib, ... }: +let + mkColorOption = with lib; description: default: mkOption { + inherit description default; + example = "#abcdef"; + type = types.strMatching "#[0-9a-f]{6}"; + }; + + cfg = config.my.home.terminal; +in +{ + imports = [ + ./termite + ]; + + options.my.home = with lib; { + terminal = { + program = mkOption { + type = with types; nullOr (enum [ "termite" ]); + default = null; + example = "termite"; + description = "Which terminal to use for home session"; + }; + + colors = { + background = mkColorOption "Background color" "#161616"; + foreground = mkColorOption "Foreground color" "#ffffff"; + foregroundBold = mkColorOption "Foreground bold color" "#ffffff"; + cursor = mkColorOption "Cursor color" "#ffffff"; + + black = mkColorOption "Black" "#222222"; + blackBold = mkColorOption "Black bold" "#666666"; + + red = mkColorOption "Red" "#e84f4f"; + redBold = mkColorOption "Red bold" "#d23d3d"; + + green = mkColorOption "Green" "#b7ce42"; + greenBold = mkColorOption "Green bold" "#bde077"; + + yellow = mkColorOption "Yellow" "#fea63c"; + yellowBold = mkColorOption "Yellow bold" "#ffe863"; + + blue = mkColorOption "Blue" "#66aabb"; + blueBold = mkColorOption "Blue bold" "#aaccbb"; + + magenta = mkColorOption "Magenta" "#b7416e"; + magentaBold = mkColorOption "Magenta bold" "#e16a98"; + + cyan = mkColorOption "Cyan" "#6d878d"; + cyanBold = mkColorOption "Cyan bold" "#42717b"; + + white = mkColorOption "White" "#dddddd"; + whiteBold = mkColorOption "White bold" "#cccccc"; + }; + }; + }; + + config.home.sessionVariables = lib.mkIf (cfg.program != null) { + TERMINAL = cfg.program; + }; +} diff --git a/home/terminal/termite/default.nix b/home/terminal/termite/default.nix new file mode 100644 index 0000000..e8f67a7 --- /dev/null +++ b/home/terminal/termite/default.nix @@ -0,0 +1,53 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.home.terminal; +in +{ + config = lib.mkIf (cfg.program == "termite") { + programs.termite = { + enable = true; + + # Niceties + browser = "${pkgs.xdg-utils}/bin/xdg-open"; + clickableUrl = true; + dynamicTitle = true; + fullscreen = false; + mouseAutohide = true; + urgentOnBell = true; + + # Look and feel + allowBold = true; + audibleBell = false; + cursorBlink = "system"; + font = "Monospace 9"; + scrollbar = "off"; + + + # Colors + backgroundColor = cfg.colors.background; + cursorColor = cfg.colors.cursor; + foregroundColor = cfg.colors.foreground; + foregroundBoldColor = cfg.colors.foregroundBold; + colorsExtra = with cfg.colors; '' + # Normal colors + color0 = ${black} + color1 = ${red} + color2 = ${green} + color3 = ${yellow} + color4 = ${blue} + color5 = ${magenta} + color6 = ${cyan} + color7 = ${white} + # Bold colors + color8 = ${blackBold} + color9 = ${redBold} + color10 = ${greenBold} + color11 = ${yellowBold} + color12 = ${blueBold} + color13 = ${magentaBold} + color14 = ${cyanBold} + color15 = ${whiteBold} + ''; + }; + }; +} diff --git a/home/tmux.nix b/home/tmux/default.nix similarity index 62% rename from home/tmux.nix rename to home/tmux/default.nix index 6b05414..f9b711c 100644 --- a/home/tmux.nix +++ b/home/tmux/default.nix @@ -1,6 +1,7 @@ { config, lib, pkgs, ... }: let cfg = config.my.home.tmux; + hasGUI = config.my.home.x.enable || (config.my.home.wm != null); in { options.my.home.tmux = with lib.my; { @@ -13,7 +14,7 @@ in keyMode = "vi"; # Home-row keys and other niceties clock24 = true; # I'm one of those heathens escapeTime = 0; # Let vim do its thing instead - historyLimit = 5000; # Bigger buffer + historyLimit = 50000; # Bigger buffer terminal = "tmux-256color"; # I want accurate termcap info plugins = with pkgs.tmuxPlugins; [ @@ -23,8 +24,16 @@ in pain-control # Better session management sessionist - # X clipboard integration - yank + (lib.optionalAttrs hasGUI { + # X clipboard integration + plugin = yank; + extraConfig = '' + # Use 'clipboard' because of misbehaving apps (e.g: firefox) + set -g @yank_selection_mouse 'clipboard' + # Stay in copy mode after yanking + set -g @yank_action 'copy-pipe' + ''; + }) { # Show when prefix has been pressed plugin = prefix-highlight; @@ -41,7 +50,13 @@ in extraConfig = '' # Better vim mode bind-key -T copy-mode-vi 'v' send -X begin-selection - bind-key -T copy-mode-vi 'y' send -X copy-selection-and-cancel + ${ + lib.optionalString + (!hasGUI) + "bind-key -T copy-mode-vi 'y' send -X copy-selection" + } + # Block selection in vim mode + bind-key -Tcopy-mode-vi 'C-v' send -X begin-selection \; send -X rectangle-toggle ''; }; } diff --git a/home/udiskie/default.nix b/home/udiskie/default.nix new file mode 100644 index 0000000..1f2119e --- /dev/null +++ b/home/udiskie/default.nix @@ -0,0 +1,13 @@ +{ config, lib, ... }: +let + cfg = config.my.home.udiskie; +in +{ + options.my.home.udiskie = with lib; { + enable = mkEnableOption "udiskie configuration"; + }; + + config.services.udiskie = lib.mkIf cfg.enable { + enable = true; + }; +} diff --git a/home/vim/after/ftplugin/bash.vim b/home/vim/after/ftplugin/bash.vim deleted file mode 100644 index 2b69ab4..0000000 --- a/home/vim/after/ftplugin/bash.vim +++ /dev/null @@ -1,14 +0,0 @@ -" Create the `b:undo_ftplugin` variable if it doesn't exist -call ftplugined#check_undo_ft() - -" Use shfmt as ALE fixer for bash -let b:ale_fixers=[ 'shfmt' ] -let b:undo_ftplugin.='|unlet! b:ale_fixers' - -" Indent with 4 spaces, simplify script, indent switch cases, use Bash variant -let b:ale_sh_shfmt_options='-i 4 -s -ci -ln bash' -let b:undo_ftplugin.='|unlet! b:ale_sh_shfmt_options' - -" Use bash dialect explicitly, require explicit empty string test -let b:ale_sh_shellcheck_options='-s bash -o avoid-nullary-conditions' -let b:undo_ftplugin.='|unlet! b:ale_sh_shellcheck_options' diff --git a/home/vim/after/ftplugin/beancount.vim b/home/vim/after/ftplugin/beancount.vim index c5645c8..a2b142e 100644 --- a/home/vim/after/ftplugin/beancount.vim +++ b/home/vim/after/ftplugin/beancount.vim @@ -7,7 +7,3 @@ let b:undo_ftplugin.='|setlocal shiftwidth<' " Have automatic padding of transactions so that decimal is on 52nd column let g:beancount_separator_col=52 - -" Automatic padding for transactions -nnoremap = :AlignCommodity -vnoremap = :AlignCommodity diff --git a/home/vim/after/ftplugin/c.vim b/home/vim/after/ftplugin/c.vim deleted file mode 100644 index a85ff07..0000000 --- a/home/vim/after/ftplugin/c.vim +++ /dev/null @@ -1,14 +0,0 @@ -" Create the `b:undo_ftplugin` variable if it doesn't exist -call ftplugined#check_undo_ft() - -" More warnings and the usual version in flags for Clang -let b:ale_c_clang_options='-Wall -Wextra -pedantic -std=c99' -let b:undo_ftplugin.='|unlet! b:ale_c_clang_options' - -" More warnings and the usual version in flags for GCC -let b:ale_c_gcc_options='-Wall -Wextra -pedantic -std=c99' -let b:undo_ftplugin.='|unlet! b:ale_c_gcc_options' - -" Use compile_commands.json to look for additional flags -let b:ale_c_parse_compile_commands=1 -let b:undo_ftplugin.='|unlet! b:ale_c_parse_compile_commands' diff --git a/home/vim/after/ftplugin/cpp.vim b/home/vim/after/ftplugin/cpp.vim deleted file mode 100644 index 4fa501e..0000000 --- a/home/vim/after/ftplugin/cpp.vim +++ /dev/null @@ -1,14 +0,0 @@ -" Create the `b:undo_ftplugin` variable if it doesn't exist -call ftplugined#check_undo_ft() - -" More warnings and the usual version in flags for Clang -let b:ale_cpp_clang_options='-Wall -Wextra -pedantic -std=c++17' -let b:undo_ftplugin.='|unlet! b:ale_cpp_clang_options' - -" More warnings and the usual version in flags for GCC -let b:ale_cpp_gcc_options='-Wall -Wextra -pedantic -std=c++17' -let b:undo_ftplugin.='|unlet! b:ale_cpp_gcc_options' - -" Use compile_commands.json to look for additional flags -let b:ale_c_parse_compile_commands=1 -let b:undo_ftplugin.='|unlet! b:ale_c_parse_compile_commands' diff --git a/home/vim/after/ftplugin/d.vim b/home/vim/after/ftplugin/d.vim deleted file mode 100644 index 0e868c7..0000000 --- a/home/vim/after/ftplugin/d.vim +++ /dev/null @@ -1,6 +0,0 @@ -" Create the `b:undo_ftplugin` variable if it doesn't exist -call ftplugined#check_undo_ft() - -" Use my desired ALE fixer for D -let b:ale_fixers=[ 'dfmt' ] -let b:undo_ftplugin.='|unlet! b:ale_fixers' diff --git a/home/vim/after/ftplugin/gitsendemail.vim b/home/vim/after/ftplugin/gitsendemail.vim deleted file mode 100644 index fc9c729..0000000 --- a/home/vim/after/ftplugin/gitsendemail.vim +++ /dev/null @@ -1,2 +0,0 @@ -" Create the `b:undo_ftplugin` variable if it doesn't exist -call ftplugined#check_undo_ft() diff --git a/home/vim/after/ftplugin/haskell.vim b/home/vim/after/ftplugin/haskell.vim index 80d7f2e..978f346 100644 --- a/home/vim/after/ftplugin/haskell.vim +++ b/home/vim/after/ftplugin/haskell.vim @@ -5,26 +5,6 @@ call ftplugined#check_undo_ft() setlocal shiftwidth=2 let b:undo_ftplugin.='|setlocal shiftwidth<' -" Use my desired ALE fixers for Haskell -let b:ale_fixers=[ 'brittany' ] -let b:undo_ftplugin.='|unlet! b:ale_fixers' - -" Use stack-managed `hlint` -let b:ale_haskell_hlint_executable='stack' -let b:undo_ftplugin.='|unlet! b:ale_haskell_hlint_executable' - -" Use stack-managed `brittany` -let b:ale_haskell_brittany_executable='stack' -let b:undo_ftplugin.='|unlet! b:ale_haskell_brittany_executable' - -" Use dynamic libraries because of Arch linux, with default ALE options -let b:ale_haskell_ghc_options='--dynamic -fno-code -v0' -let b:undo_ftplugin.='|unlet! b:ale_haskell_ghc_options' - -" Automatically format files when saving them -let b:ale_fix_on_save=1 -let b:undo_ftplugin='|unlet! b:ale_lint_on_save' - " Change max length of a line to 100 for this buffer to match official guidelines setlocal colorcolumn=100 let b:undo_ftplugin.='|setlocal colorcolumn<' diff --git a/home/vim/after/ftplugin/json.vim b/home/vim/after/ftplugin/json.vim deleted file mode 100644 index 2b4ba56..0000000 --- a/home/vim/after/ftplugin/json.vim +++ /dev/null @@ -1,6 +0,0 @@ -" Create the `b:undo_ftplugin` variable if it doesn't exist -call ftplugined#check_undo_ft() - -" Use my desired ALE fixer for JSON -let b:ale_fixers=[ 'jq' ] -let b:undo_ftplugin.='|unlet! b:ale_fixers' diff --git a/home/vim/after/ftplugin/pandoc.vim b/home/vim/after/ftplugin/pandoc.vim index d44bc12..5b58b41 100644 --- a/home/vim/after/ftplugin/pandoc.vim +++ b/home/vim/after/ftplugin/pandoc.vim @@ -1,10 +1,6 @@ " Create the `b:undo_ftplugin` variable if it doesn't exist call ftplugined#check_undo_ft() -" Let ALE know that I want Markdown linters -let b:ale_linter_aliases=[ 'markdown' ] -let b:undo_ftplugin.='|unlet! b:ale_linter_aliases' - " Use a small indentation value on Pandoc files setlocal shiftwidth=2 let b:undo_ftplugin.='|setlocal shiftwidth<' diff --git a/home/vim/after/ftplugin/python.vim b/home/vim/after/ftplugin/python.vim deleted file mode 100644 index a18299e..0000000 --- a/home/vim/after/ftplugin/python.vim +++ /dev/null @@ -1,40 +0,0 @@ -" Create the `b:undo_ftplugin` variable if it doesn't exist -call ftplugined#check_undo_ft() - -" Use my desired ALE fixers for python -let b:ale_fixers=[ 'black', 'isort' ] -let b:undo_ftplugin.='|unlet! b:ale_fixers' -" Use my desired ALE linters for python -let b:ale_linters=[ 'flake8', 'mypy', 'pylint', 'pyls' ] -let b:undo_ftplugin.='|unlet! b:ale_linters' - -" Use pyls inside the python environment if needed -let b:ale_python_pyls_auto_pipenv=1 -let b:undo_ftplugin.='|unlet! b:ale_python_pyls_auto_pipenv' - -" Disable pycodestyle checks from pyls because I'm already using flake8 -let b:ale_python_pyls_config={ - \ 'pyls': { - \ 'plugins': { - \ 'pycodestyle': { - \ 'enabled': v:false - \ }, - \ }, - \ }, - \ } -let b:undo_ftplugin.='|unlet! b:ale_python_pyls_config' - -" Don't use mypy to check for syntax errors -let b:ale_python_mypy_ignore_invalid_syntax=1 -let b:undo_ftplugin.='|unlet! b:ale_python_mypy_ignore_invalid_syntax' -" Use mypy inside the python environment if needed -let b:ale_python_mypy_auto_pipenv=1 -let b:undo_ftplugin.='|unlet! b:ale_python_mypy_auto_pipenv' - -" Automatically format files when saving them -let b:ale_fix_on_save=1 -let b:undo_ftplugin='|unlet! b:ale_lint_on_save' - -" Change max length of a line to 88 for this buffer to match black's settings -setlocal colorcolumn=88 -let b:undo_ftplugin.='|setlocal colorcolumn<' diff --git a/home/vim/after/ftplugin/qf.vim b/home/vim/after/ftplugin/qf.vim deleted file mode 100644 index 01036f9..0000000 --- a/home/vim/after/ftplugin/qf.vim +++ /dev/null @@ -1,8 +0,0 @@ -" Create the `b:undo_ftplugin` variable if it doesn't exist -call ftplugined#check_undo_ft() - -" Use h/l to go to the previous/next non-empty quickfix or location list -nnoremap h :call quickfixed#older() -let b:undo_ftplugin.='|nunmap h' -nnoremap l :call quickfixed#newer() -let b:undo_ftplugin.='|nunmap l' diff --git a/home/vim/after/ftplugin/rust.vim b/home/vim/after/ftplugin/rust.vim index 61516f9..8738a54 100644 --- a/home/vim/after/ftplugin/rust.vim +++ b/home/vim/after/ftplugin/rust.vim @@ -1,31 +1,6 @@ " Create the `b:undo_ftplugin` variable if it doesn't exist call ftplugined#check_undo_ft() -" Check tests too -let b:ale_rust_cargo_check_tests=1 -let b:undo_ftplugin='|unlet! b:ale_rust_cargo_check_tests' - -" Check examples too -let b:ale_rust_cargo_check_examples=1 -let b:undo_ftplugin='|unlet! b:ale_rust_cargo_check_examples' - -" Use clippy if it's available instead of just cargo check -let b:ale_rust_cargo_use_clippy=executable('cargo-clippy') -let b:undo_ftplugin='|unlet! b:ale_rust_cargo_use_clippy' - -" Use rust-analyzer instead of RLS as a linter -let b:ale_linters=[ 'cargo', 'analyzer' ] -let b:undo_ftplugin='|unlet! b:ale_linters' - - -" Use rustfmt as ALE fixer for rust -let b:ale_fixers=[ 'rustfmt' ] -let b:undo_ftplugin.='|unlet! b:ale_fixers' - -" Automatically format files when saving them -let b:ale_fix_on_save=1 -let b:undo_ftplugin='|unlet! b:ale_lint_on_save' - " Change max length of a line to 99 for this buffer to match official guidelines setlocal colorcolumn=99 let b:undo_ftplugin.='|setlocal colorcolumn<' diff --git a/home/vim/after/ftplugin/sh.vim b/home/vim/after/ftplugin/sh.vim deleted file mode 100644 index 5b5a88c..0000000 --- a/home/vim/after/ftplugin/sh.vim +++ /dev/null @@ -1,14 +0,0 @@ -" Create the `b:undo_ftplugin` variable if it doesn't exist -call ftplugined#check_undo_ft() - -" Use shfmt as ALE fixer for sh -let b:ale_fixers=[ 'shfmt' ] -let b:undo_ftplugin.='|unlet! b:ale_fixers' - -" Indent with 4 spaces, simplify the code, indent switch cases, use POSIX -let b:ale_sh_shfmt_options='-i 4 -s -ci -ln posix' -let b:undo_ftplugin.='|unlet! b:ale_sh_shfmt_options' - -" Require explicit empty string test -let b:ale_sh_shellcheck_options='-o avoid-nullary-conditions' -let b:undo_ftplugin.='|unlet! b:ale_sh_shellcheck_options' diff --git a/home/vim/after/ftplugin/tiger.vim b/home/vim/after/ftplugin/tiger.vim new file mode 100644 index 0000000..81c2cfc --- /dev/null +++ b/home/vim/after/ftplugin/tiger.vim @@ -0,0 +1,6 @@ +" Create the `b:undo_ftplugin` variable if it doesn't exist +call ftplugined#check_undo_ft() + +" Use a small indentation value on tiger files +setlocal shiftwidth=2 +let b:undo_ftplugin.='|setlocal shiftwidth<' diff --git a/home/vim/after/ftplugin/zsh.vim b/home/vim/after/ftplugin/zsh.vim deleted file mode 100644 index b8c7a31..0000000 --- a/home/vim/after/ftplugin/zsh.vim +++ /dev/null @@ -1,14 +0,0 @@ -" Create the `b:undo_ftplugin` variable if it doesn't exist -call ftplugined#check_undo_ft() - -" Use shfmt as ALE fixer for zsh -let b:ale_fixers=[ 'shfmt' ] -let b:undo_ftplugin.='|unlet! b:ale_fixers' - -" Indent with 4 spaces, simplify script, indent switch cases, use Bash variant -let b:ale_sh_shfmt_options='-i 4 -s -ci -ln bash' -let b:undo_ftplugin.='|unlet! b:ale_sh_shfmt_options' - -" Use bash dialect explicitly, require explicit empty string test -let b:ale_sh_shellcheck_options='-s bash -o avoid-nullary-conditions' -let b:undo_ftplugin.='|unlet! b:ale_sh_shellcheck_options' diff --git a/home/vim/after/plugin/mappings/ale.vim b/home/vim/after/plugin/mappings/ale.vim deleted file mode 100644 index 3069f81..0000000 --- a/home/vim/after/plugin/mappings/ale.vim +++ /dev/null @@ -1,2 +0,0 @@ -" Use ALE LSP-powered symbol information -nnoremap K :ALEHover diff --git a/home/vim/after/plugin/mappings/commentary.vim b/home/vim/after/plugin/mappings/commentary.vim new file mode 100644 index 0000000..219d661 --- /dev/null +++ b/home/vim/after/plugin/mappings/commentary.vim @@ -0,0 +1,12 @@ +lua << EOF +local wk = require("which-key") + +local keys = { + name = "Comment/uncomment", + c = "Current line", + u = "Uncomment the current and adjacent commented lines", + ["gc"] = "Uncomment the current and adjacent commented lines", +} + +wk.register(keys, { prefix = "gc" }) +EOF diff --git a/home/vim/after/plugin/mappings/fugitive.vim b/home/vim/after/plugin/mappings/fugitive.vim deleted file mode 100644 index 934f00e..0000000 --- a/home/vim/after/plugin/mappings/fugitive.vim +++ /dev/null @@ -1,10 +0,0 @@ -" Visual bindings for merging diffs as in normal mode -xnoremap dp :diffput -xnoremap do :diffget - -" Open status window -nnoremap gs :Gstatus -" Open diff view of current buffer: the up/left window is the current index -nnoremap gd :Gdiffsplit! -" Open current file log in new tab, populate its location list with history -nnoremap gl :spT:Gllog --follow -- %:p diff --git a/home/vim/after/plugin/mappings/fzf.vim b/home/vim/after/plugin/mappings/fzf.vim deleted file mode 100644 index 8cfb8b8..0000000 --- a/home/vim/after/plugin/mappings/fzf.vim +++ /dev/null @@ -1,38 +0,0 @@ -" Only git-tracked files, Vim needs to be in a Git repository -nnoremap fg :GFiles - -" All files -nnoremap ff :Files - -" Currently open buffers -nnoremap fb :Buffers - -" Buffer history -nnoremap fh :History - -" Tags in buffer -nnoremap ft :BTags - -" Tags in all project files -nnoremap fT :Tags - -" Snippets for the current fileytpe (using Ultisnips) -nnoremap fs :Snippets - -" All available commands -nnoremap f: :Commands - -" All commits (using fugitive) -nnoremap fc :Commits - -" All commits for the current buffer (using fugitive) -nnoremap fC :BCommits - -" Select normal mode mapping by searching for its name -nmap (fzf-maps-n) - -" Select visual mode mapping by searching for its name -xmap (fzf-maps-x) - -" Select operator pending mode mapping by searching for its name -omap (fzf-maps-o) diff --git a/home/vim/after/plugin/mappings/misc.vim b/home/vim/after/plugin/mappings/misc.vim index 7f1ea1c..c7fcc8a 100644 --- a/home/vim/after/plugin/mappings/misc.vim +++ b/home/vim/after/plugin/mappings/misc.vim @@ -1,8 +1,9 @@ -" Yank until the end of line with Y, to be more consistent with D and C -nnoremap Y y$ +lua << EOF +local wk = require("which-key") -" Run make silently, then skip the 'Press ENTER to continue' -noremap m :silent! :make! \| :redraw! +local keys = { + [""] = { "nohls", "Clear search highlight" }, +} -" Remove search-highlighting -noremap :nohls +wk.register(keys, { prefix = "" }) +EOF diff --git a/home/vim/after/plugin/mappings/qf.vim b/home/vim/after/plugin/mappings/qf.vim deleted file mode 100644 index c5e353b..0000000 --- a/home/vim/after/plugin/mappings/qf.vim +++ /dev/null @@ -1,11 +0,0 @@ -" Next and previous in quick-fix list -nmap fn (qf_qf_next) -nmap fp (qf_qf_previous) - -" Next and previous in location list -nmap ln (qf_loc_next) -nmap lp (qf_loc_previous) - -" Toggle quick-fix and location lists -nmap tf (qf_qf_toggle) -nmap tl (qf_loc_toggle) diff --git a/home/vim/after/plugin/mappings/telescope.vim b/home/vim/after/plugin/mappings/telescope.vim new file mode 100644 index 0000000..eb6363d --- /dev/null +++ b/home/vim/after/plugin/mappings/telescope.vim @@ -0,0 +1,18 @@ +lua << EOF +local wk = require("which-key") +local telescope_builtin = require("telescope.builtin") + +local keys = { + f = { + name = "Fuzzy finder", + b = { telescope_builtin.buffers, "Open buffers" }, + f = { telescope_builtin.git_files, "Git tracked files" }, + F = { telescope_builtin.find_files, "Files" }, + g = { telescope_builtin.live_grep, "Grep string" }, + G = { telescope_builtin.grep_string, "Grep string under cursor" }, + }, +} + +wk.register(keys, { prefix = "" }) +EOF + diff --git a/home/vim/after/plugin/mappings/tree-sitter-textobjects.vim b/home/vim/after/plugin/mappings/tree-sitter-textobjects.vim new file mode 100644 index 0000000..9cabd91 --- /dev/null +++ b/home/vim/after/plugin/mappings/tree-sitter-textobjects.vim @@ -0,0 +1,32 @@ +lua << EOF +local wk = require("which-key") + +local motions = { + ["]m"] = "Next method start", + ["]M"] = "Next method end", + ["]S"] = "Next statement start", + ["]]"] = "Next class start", + ["]["] = "Next class end", + ["[m"] = "Previous method start", + ["[M"] = "Previous method end", + ["[S"] = "Previous statement start", + ["[["] = "Previous class start", + ["[]"] = "Previous class end", +} + +local objects = { + ["aa"] = "a parameter", + ["ia"] = "inner parameter", + ["ab"] = "a block", + ["ib"] = "inner block", + ["ac"] = "a class", + ["ic"] = "inner class", + ["af"] = "a function", + ["if"] = "inner function", + ["ak"] = "a comment", + ["aS"] = "a statement", +} + +wk.register(motions, { mode = "n" }) +wk.register(objects, { mode = "o" }) +EOF diff --git a/home/vim/after/plugin/mappings/unimpaired.vim b/home/vim/after/plugin/mappings/unimpaired.vim index 53457bd..c4bb35b 100644 --- a/home/vim/after/plugin/mappings/unimpaired.vim +++ b/home/vim/after/plugin/mappings/unimpaired.vim @@ -1,7 +1,125 @@ -" Better fr layout mappings for vim-unimpaired and other '[' and ']' commands -nmap ( [ -nmap ) ] -omap ( [ -omap ) ] -xmap ( [ -xmap ) ] +lua << EOF +local wk = require("which-key") + +local keys = { + -- Edition and navigation mappins + ["["] = { + name = "Previous", + [""] = "Insert blank line above", + [""] = "Previous location list file", + [""] = "Previous quickfix list file", + [""] = "Previous tag in preview window", + a = "Previous argument", + A = "First argument", + b = "Previous buffer", + B = "First buffer", + e = "Exchange previous line", + f = "Previous file in directory", + l = "Previous location list entry", + L = "First Location list entry", + n = "Previous conflict marker/diff hunk", + p = "Paste line above", + P = "Paste line above", + q = "Previous quickfix list entry", + Q = "First quickfix list entry", + t = "Previous matching tag", + T = "First matching tag", + z = "Previous fold", + -- Encoding + C = "C string encode", + u = "URL encode", + x = "XML encode", + y = "C string encode", + -- Custom + d = { vim.diagnostic.goto_prev, "Previous diagnostic" } + }, + ["]"] = { + name = "Next", + [""] = "Insert blank line below", + [""] = "Next location list file", + [""] = "Next quickfix list file", + [""] = "Next tag in preview window", + a = "Next argument", + A = "Last argument", + b = "Next buffer", + B = "Last buffer", + e = "Exchange next line", + f = "Next file in directory", + l = "Next location list entry", + L = "Last Location list entry", + n = "Next conflict marker/diff hunk", + p = "Paste line below", + P = "Paste line below", + q = "Next quickfix list entry", + Q = "Last quickfix list entry", + t = "Next matching tag", + T = "Last matching tag", + z = "Next fold", + -- Decoding + C = "C string decode", + u = "URL decode", + x = "XML decode", + y = "C string decode", + -- Custom + d = { vim.diagnostic.goto_next, "Next diagnostic" } + }, + + -- Option mappings + ["[o"] = { + name = "Enable option", + b = "Light background", + c = "Cursor line", + d = "Diff", + e = { "lwindow", "Location list" }, + f = { "cwindow", "Quickfix list" }, + h = "Search high-lighting", + i = "Case insensitive search", + l = "List mode", + n = "Line numbers", + r = "Relative line numbers", + u = "Cursor column", + v = "Virtual editing", + w = "Text wrapping", + x = "Cursor line and column", + z = "Spell checking", + }, + ["]o"] = { + name = "Option off", + b = "Light background", + c = "Cursor line", + d = "Diff", + e = { "lclose", "Location list" }, + f = { "cclose", "Quickfix list" }, + h = "Search high-lighting", + i = "Case insensitive search", + l = "List mode", + n = "Line numbers", + r = "Relative line numbers", + u = "Cursor column", + v = "Virtual editing", + w = "Text wrapping", + x = "Cursor line and column", + z = "Spell checking", + }, + ["yo"] = { + name = "Option toggle", + b = "Light background", + c = "Cursor line", + d = "Diff", + e = { "(qf_loc_toggle)", "Location list" }, + f = { "(qf_qf_toggle)", "Quickfix list" }, + h = "Search high-lighting", + i = "Case insensitive search", + l = "List mode", + n = "Line numbers", + r = "Relative line numbers", + u = "Cursor column", + v = "Virtual editing", + w = "Text wrapping", + x = "Cursor line and column", + z = "Spell checking", + }, +} + +wk.register(keys) +EOF diff --git a/home/vim/autoload/quickfixed.vim b/home/vim/autoload/quickfixed.vim deleted file mode 100644 index 4862f8f..0000000 --- a/home/vim/autoload/quickfixed.vim +++ /dev/null @@ -1,68 +0,0 @@ -" Taken from the Vimways article - -function! s:isLocation() - " Get dictionary of properties of the current window - let wininfo = filter(getwininfo(), {i,v -> v.winnr == winnr()})[0] - return wininfo.loclist -endfunction - -function! s:length() - " Get the size of the current quickfix/location list - return len(s:isLocation() ? getloclist(0) : getqflist()) -endfunction - -function! s:getProperty(key, ...) - " getqflist() and getloclist() expect a dictionary argument - " If a 2nd argument has been passed in, use it as the value, else 0 - let l:what = {a:key : a:0 ? a:1 : 0} - let l:listdict = s:isLocation() ? getloclist(0, l:what) : getqflist(l:what) - return get(l:listdict, a:key) -endfunction - -function! s:isFirst() - return s:getProperty('nr') <= 1 -endfunction - -function! s:isLast() - return s:getProperty('nr') == s:getProperty('nr', '$') -endfunction - -function! s:history(goNewer) - " Build the command: one of colder/cnewer/lolder/lnewer - let l:cmd = (s:isLocation() ? 'l' : 'c') . (a:goNewer ? 'newer' : 'older') - - " Apply the cmd repeatedly until we hit a non-empty list, or first/last list - " is reached - while 1 - if (a:goNewer && s:isLast()) || (!a:goNewer && s:isFirst()) | break | endif - " Run the command. Use :silent to suppress message-history output. - " Note that the :try wrapper is no longer necessary - silent execute l:cmd - if s:length() | break | endif - endwhile - - " Echo a description of the new quickfix / location list. - " And make it look like a rainbow. - let l:nr = s:getProperty('nr') - let l:last = s:getProperty('nr', '$') - echohl MoreMsg | echon '(' - echohl Identifier | echon l:nr - if l:last > 1 - echohl LineNr | echon ' of ' - echohl Identifier | echon l:last - endif - echohl MoreMsg | echon ') ' - echohl MoreMsg | echon '[' - echohl Identifier | echon s:length() - echohl MoreMsg | echon '] ' - echohl Normal | echon s:getProperty('title') - echohl None -endfunction - -function! quickfixed#older() - call s:history(0) -endfunction - -function! quickfixed#newer() - call s:history(1) -endfunction diff --git a/home/vim/default.nix b/home/vim/default.nix index 08f5ecd..2f17890 100644 --- a/home/vim/default.nix +++ b/home/vim/default.nix @@ -13,6 +13,7 @@ let "after" "autoload" "ftdetect" + "lua" "plugin" ]; in @@ -30,11 +31,9 @@ in plugins = with pkgs.vimPlugins; [ # Theming - lightline-vim # Fancy status bar - { - plugin = onedark-vim; # Nice dark theme - optional = true; # Needs to be `packadd`-ed manually... - } + vim-gruvbox8 # Nice dark theme + lualine-nvim # A lua-based status line + lualine-lsp-progress # Show progress for LSP servers # tpope essentials vim-commentary # Easy comments @@ -43,7 +42,6 @@ in vim-git # Sane git syntax files vim-repeat # Enanche '.' for plugins vim-rsi # Readline mappings - vim-surround # Deal with pairs vim-unimpaired # Some ex command mappings vim-vinegar # Better netrw @@ -57,20 +55,54 @@ in vim-toml # General enhancements - fastfold # Better folding vim-qf # Better quick-fix list # Other wrappers - fzfWrapper # The vim plugin inside the 'fzf' package - fzf-vim # Fuzzy commands git-messenger-vim # A simple blame window # LSP and linting - ale # Asynchronous Linting Engine - lightline-ale # Status bar integration + nvim-lspconfig # Easy LSP configuration + lsp_lines-nvim # Show diagnostics *over* regions + null-ls-nvim # LSP integration for linters and formatters + (nvim-treesitter.withPlugins (_: pkgs.tree-sitter.allGrammars)) # Better highlighting + nvim-treesitter-textobjects # More textobjects + nvim-ts-context-commentstring # Comment string in nested language blocks + plenary-nvim # 'null-ls', 'telescope' dependency + + # Completion + nvim-cmp # Completion engine + cmp-buffer # Words from open buffers + cmp-nvim-lsp # LSP suggestions + cmp-nvim-lua # NeoVim lua API + cmp-path # Path name suggestions + cmp-under-comparator # Sort items that start with '_' lower + friendly-snippets # LSP snippets collection + luasnip # Snippet manager compatible with LSP + + # UX improvements + dressing-nvim # Integrate native UI hooks with Telescope etc... + gitsigns-nvim # Fast git UI integration + nvim-surround # Deal with pairs, now in Lua + telescope-fzf-native-nvim # Use 'fzf' fuzzy matching algorithm + telescope-lsp-handlers-nvim # Use 'telescope' for various LSP actions + telescope-nvim # Fuzzy finder interface + which-key-nvim # Show available mappings ]; extraConfig = builtins.readFile ./init.vim; + + # Linters, formatters, etc... + extraPackages = with pkgs; [ + # C/C++ + clang-tools + + # Nix + nixpkgs-fmt + + # Shell + shellcheck + shfmt + ]; }; config.xdg.configFile = lib.mkIf cfg.enable configFiles; diff --git a/home/vim/ftdetect/tiger.vim b/home/vim/ftdetect/tiger.vim new file mode 100644 index 0000000..d474fd7 --- /dev/null +++ b/home/vim/ftdetect/tiger.vim @@ -0,0 +1,3 @@ +" Use Tiger filetype for programs and header files +au BufNewFile,BufRead *.tig setfiletype tiger +au BufNewFile,BufRead *.tih setfiletype tiger diff --git a/home/vim/init.vim b/home/vim/init.vim index 8968204..e5e863c 100644 --- a/home/vim/init.vim +++ b/home/vim/init.vim @@ -13,6 +13,13 @@ set wildmenu " Enable syntax high-lighting and file-type specific plugins syntax on filetype plugin indent on + +" Map leader to space (needs the noremap trick to avoid moving the cursor) +nnoremap +let mapleader=" " + +" Map localleader to '!' (if I want to filter text, I use visual mode) +let maplocalleader="!" " }}} " Indentation configuration {{{ @@ -65,13 +72,20 @@ set lazyredraw " Timeout quickly on shortcuts, I can't wait two seconds to delete in visual set timeoutlen=500 +" Timeout quickly for CursorHold events (and also swap file) +set updatetime=250 + " Set dark mode by default set background=dark -" Load it manually because of autoload functions... -packadd! onedark-vim -" Use onedark -colorscheme onedark +" Include plug-in integration +let g:gruvbox_plugin_hi_groups=1 +" Include filetype integration +let g:gruvbox_filetype_hi_groups=1 +" 24 bit colors +set termguicolors +" Use my preferred colorscheme +colorscheme gruvbox8 " }}} " Search parameters {{{ diff --git a/home/vim/lua/ambroisie/lsp.lua b/home/vim/lua/ambroisie/lsp.lua new file mode 100644 index 0000000..af87950 --- /dev/null +++ b/home/vim/lua/ambroisie/lsp.lua @@ -0,0 +1,84 @@ +local M = {} + +-- shared LSP configuration callback +-- @param client native client configuration +-- @param bufnr int? buffer number of the attched client +M.on_attach = function(client, bufnr) + -- Diagnostics + vim.diagnostic.config({ + -- Disable virtual test next to affected regions + virtual_text = false, + -- Show diagnostics signs + signs = true, + -- Underline offending regions + underline = true, + -- Do not bother me in the middle of insertion + update_in_insert = false, + -- Show highest severity first + severity_sort = true, + }) + + vim.cmd([[ + augroup DiagnosticsHover + autocmd! * + " Show diagnostics on "hover" + autocmd CursorHold,CursorHoldI lua vim.diagnostic.open_float(nil, {focus=false, scope="cursor"}) + augroup END + ]]) + + -- Format on save + if client.resolved_capabilities.document_formatting then + vim.cmd([[ + augroup LspFormatting + autocmd! * + autocmd BufWritePre lua vim.lsp.buf.formatting_sync() + augroup END + ]]) + end + + -- Mappings + local wk = require("which-key") + + local function list_workspace_folders() + local utils = require("ambroisie.utils") + utils.dump(vim.lsp.buf.list_workspace_folders()) + end + + local function show_line_diagnostics() + vim.diagnostic.open_float(nil, { scope="line" }) + end + + local function show_buffer_diagnostics() + vim.diagnostic.open_float(nil, { scope="buffer" }) + end + + local keys = { + K = { vim.lsp.buf.hover, "Show symbol information" }, + [""] = { vim.lsp.buf.signature_help, "Show signature information" }, + ["gd"] = { vim.lsp.buf.definition, "Go to definition" }, + ["gD"] = { vim.lsp.buf.declaration, "Go to declaration" }, + ["gi"] = { vim.lsp.buf.implementation, "Go to implementation" }, + ["gr"] = { vim.lsp.buf.references, "List all references" }, + + ["c"] = { + name = "Code", + a = { vim.lsp.buf.code_action, "Code actions" }, + d = { show_line_diagnostics, "Show line diagnostics" }, + D = { show_buffer_diagnostics, "Show buffer diagnostics" }, + r = { vim.lsp.buf.rename, "Rename symbol" }, + s = { vim.lsp.buf.signature_help, "Show signature" }, + t = { vim.lsp.buf.type_definition, "Go to type definition" }, + w = { + name = "Workspace", + a = { vim.lsp.buf.add_workspace_folder, "Add folder to workspace" }, + l = { list_workspace_folders, "List folders in workspace" }, + r = { vim.lsp.buf.remove_workspace_folder, "Remove folder from workspace" }, + }, + }, + } + + wk.register(keys, { buffer = bufnr }) +end + + +return M diff --git a/home/vim/lua/ambroisie/utils.lua b/home/vim/lua/ambroisie/utils.lua new file mode 100644 index 0000000..88f3d27 --- /dev/null +++ b/home/vim/lua/ambroisie/utils.lua @@ -0,0 +1,37 @@ +local M = {} + +-- pretty print lua object +-- @param obj any object to pretty print +M.dump = function(obj) + print(vim.inspect(obj)) +end + +--- checks if a given command is executable +---@param cmd string? command to check +---@return boolean executable +M.is_executable = function(cmd) + return cmd and vim.fn.executable(cmd) == 1 +end + +--- return a function that checks if a given command is executable +---@param cmd string? command to check +---@return fun(cmd: string): boolean executable +M.is_executable_condition = function(cmd) + return function() return M.is_executable(cmd) end +end + +-- list all active LSP clients for current buffer +-- @param bufnr int? buffer number +-- @return table all active LSP client names +M.list_lsp_clients = function(bufnr) + local clients = vim.lsp.buf_get_clients(bufnr) + local names = {} + + for _, client in ipairs(clients) do + table.insert(names, client.name) + end + + return names +end + +return M diff --git a/home/vim/plugin/mappings/leader.vim b/home/vim/plugin/mappings/leader.vim deleted file mode 100644 index 0aba0b2..0000000 --- a/home/vim/plugin/mappings/leader.vim +++ /dev/null @@ -1,6 +0,0 @@ -" Map leader to space (needs the noremap trick to avoid moving the cursor) -nnoremap -let mapleader=" " - -" Map localleader to '!' (if I want to filter text, I use visual mode) -let maplocalleader="!" diff --git a/home/vim/plugin/settings/ale.vim b/home/vim/plugin/settings/ale.vim deleted file mode 100644 index 23c9138..0000000 --- a/home/vim/plugin/settings/ale.vim +++ /dev/null @@ -1,24 +0,0 @@ -" Always display the sign column to avoid moving the buffer all the time -let g:ale_sign_column_always=1 - -" Change the way ALE display messages -let g:ale_echo_msg_info_str='I' -let g:ale_echo_msg_warning_str='W' -let g:ale_echo_msg_error_str='E' - -" The message displayed in the command line area -let g:ale_echo_msg_format='[%linter%][%severity%]%(code):% %s' - -" The message displayed in the location list -let g:ale_loclist_msg_format='[%linter%]%(code):% %s' - -" Don't lint every time I change the buffer -let g:ale_lint_on_text_changed=0 -" Don't lint on leaving insert mode -let g:ale_lint_on_insert_leave=0 -" Don't lint on entering a buffer -let g:ale_lint_on_enter=0 -" Do lint on save -let g:ale_lint_on_save=1 -" Lint on changing the filetype -let g:ale_lint_on_filetype_changed=1 diff --git a/home/vim/plugin/settings/completion.vim b/home/vim/plugin/settings/completion.vim new file mode 100644 index 0000000..43f6795 --- /dev/null +++ b/home/vim/plugin/settings/completion.vim @@ -0,0 +1,64 @@ +" Show completion menu in all cases, and don't select anything +set completeopt=menu,menuone,noselect + +lua << EOF +local cmp = require("cmp") +local cmp_under_comparator = require("cmp-under-comparator") +local luasnip = require("luasnip") + +cmp.setup({ + snippet = { + expand = function(args) + luasnip.lsp_expand(args.body) + end, + }, + mapping = { + [""] = function(fallback) + if luasnip.expand_or_jumpable() then + luasnip.expand_or_jump() + else + fallback() + end + end, + [""] = function(fallback) + if luasnip.jumpable(-1) then + luasnip.jump(-1) + else + fallback() + end + end, + [""] = cmp.mapping(cmp.mapping.select_next_item({ behavior = cmp.SelectBehavior.Insert }), { "i", "c" }), + [""] = cmp.mapping(cmp.mapping.select_prev_item({ behavior = cmp.SelectBehavior.Insert }), { "i", "c" }), + [""] = cmp.mapping.scroll_docs(-5), + [""] = cmp.mapping.scroll_docs(5), + [""] = cmp.mapping.confirm({ behavior = cmp.ConfirmBehavior.Insert, select = false }), + [""] = cmp.mapping.abort(), + }, + view = { + entries = "native", + }, + sources = { + { name = "path", priority_weight = 110 }, + { name = "nvim_lsp", priority_weight = 100 }, + { name = "nvim_lua", priority_weight = 90 }, + { name = "luasnip", priority_weight = 80 }, + { name = "buffer", max_item_count = 5, priority_weight = 50 }, + }, + sorting = { + priority_weight = 100, + comparators = { + cmp.config.compare.offset, + cmp.config.compare.exact, + cmp.config.compare.score, + cmp_under_comparator.under, + cmp.config.compare.kind, + cmp.config.compare.sort_text, + cmp.config.compare.length, + cmp.config.compare.order, + }, + }, + experimental = { + ghost_text = true, + }, +}) +EOF diff --git a/home/vim/plugin/settings/dressing.vim b/home/vim/plugin/settings/dressing.vim new file mode 100644 index 0000000..9508126 --- /dev/null +++ b/home/vim/plugin/settings/dressing.vim @@ -0,0 +1,8 @@ +lua << EOF +local dressing = require("dressing") + +dressing.setup({ + -- Use a relative prompt size + prefer_width = 0.4, +}) +EOF diff --git a/home/vim/plugin/settings/fzf.vim b/home/vim/plugin/settings/fzf.vim deleted file mode 100644 index 7125b70..0000000 --- a/home/vim/plugin/settings/fzf.vim +++ /dev/null @@ -1,8 +0,0 @@ -" Use a floating window when availble -if has('nvim-0.4.0') || has("patch-8.2.0191") - let g:fzf_layout = { 'window': { - \ 'width': 0.9, - \ 'height': 0.7, - \ 'highlight': 'Comment', - \ 'rounded': v:false } } -endif diff --git a/home/vim/plugin/settings/git.vim b/home/vim/plugin/settings/git.vim new file mode 100644 index 0000000..8839fc9 --- /dev/null +++ b/home/vim/plugin/settings/git.vim @@ -0,0 +1,63 @@ +lua << EOF +local gitsigns = require('gitsigns') +local wk = require("which-key") + +gitsigns.setup({ + -- I dislike the full-green sign column when this happens + attach_to_untracked = false, + + current_line_blame_opts = { + -- Show the blame quickly + delay = 100, + }, +}) + +local keys = { + -- Navigation + ["[c"] = { "&diff ? '[c' : 'Gitsigns prev_hunk'", "Previous hunk/diff", expr = true }, + ["]c"] = { "&diff ? ']c' : 'Gitsigns next_hunk'", "Next hunk/diff", expr = true }, + + + -- Commands + ["g"] = { + name = "Git", + -- Actions + b = { gitsigns.toggle_current_line_blame, "Toggle blame virtual text" }, + d = { gitsigns.diffthis, "Diff buffer" }, + D = { function() gitsigns.diffthis("~") end, "Diff buffer against last commit" }, + g = { "Git", "Git status" }, + h = { gitsigns.toggle_deleted, "Show deleted hunks" }, + L = { ":spT:Gllog --follow -- %:p", "Current buffer log" }, + m = { "(git-messenger)", "Current line blame" }, + p = { gitsigns.preview_hunk, "Preview hunk" }, + r = { gitsigns.reset_hunk, "Restore hunk" }, + R = { gitsigns.reset_buffer, "Restore buffer" }, + s = { gitsigns.stage_hunk, "Stage hunk" }, + S = { gitsigns.stage_buffer, "Stage buffer" }, + u = { gitsigns.undo_stage_hunk, "Undo stage hunk" }, + ["["] = { gitsigns.prev_hunk, "Previous hunk" }, + ["]"] = { gitsigns.next_hunk, "Next hunk" }, + }, +} + +local objects = { + ["ih"] = { gitsigns.select_hunk, "Git hunk" }, +} + +local visual = { + ["ih"] = { gitsigns.select_hunk, "Git hunk" }, + + -- Only the actual command can make use of the visual selection... + ["g"] = { + name = "Git", + p = { ":Gitsigns preview_hunk", "Preview selection" }, + r = { ":Gitsigns reset_hunk", "Restore selection" }, + s = { ":Gitsigns stage_hunk", "Stage selection" }, + u = { ":Gitsigns undo_stage_hunk", "Undo stage selection" }, + }, +} + +wk.register(keys, { buffer = bufnr }) +wk.register(objects, { buffer = bufnr, mode = "o" }) +wk.register(visual, { buffer = bufnr, mode = "x" }) +EOF diff --git a/home/vim/plugin/settings/gruvbox.vim b/home/vim/plugin/settings/gruvbox.vim deleted file mode 100644 index 4b14437..0000000 --- a/home/vim/plugin/settings/gruvbox.vim +++ /dev/null @@ -1,5 +0,0 @@ -" Use the high-contrast theme -let g:gruvbox_contrast_dark='hard' - -" Enable italics because urxvt supports them -let g:gruvbox_italic=1 diff --git a/home/vim/plugin/settings/lightline.vim b/home/vim/plugin/settings/lightline.vim deleted file mode 100644 index 0513160..0000000 --- a/home/vim/plugin/settings/lightline.vim +++ /dev/null @@ -1,81 +0,0 @@ -" Initialise light-line setting structure -let g:lightline={} - -" Use the wombat colorscheme -let g:lightline.colorscheme='onedark' - -" Status-line for active buffer -let g:lightline.active={ - \ 'left': [ - \ [ 'mode', 'paste' ], - \ [ 'gitbranch', 'readonly', 'filename', 'modified' ], - \ [ 'spell' ], - \ ], - \ 'right': [ - \ [ 'lineinfo' ], - \ [ 'percent' ], - \ [ 'fileformat', 'fileencoding', 'filetype' ], - \ [ 'linter_check', 'linter_errors', 'linter_warn', 'linter_ok' ], - \ [ 'ctags_status' ], - \ ] - \ } - -" Status-line for inactive buffer -let g:lightline.inactive={ - \ 'left': [ - \ [ 'filename' ], - \ ], - \ 'right': [ - \ [ 'lineinfo' ], - \ [ 'percent' ], - \ ], - \ } - -" Which component should be written using which function -let g:lightline.component_function={ - \ 'readonly': 'LightlineReadonly', - \ 'modified': 'LightlineModified', - \ 'gitbranch': 'LightlineFugitive', - \ } - -" Which component can be expanded by using which function -let g:lightline.component_expand={ - \ 'linter_check': 'lightline#ale#checking', - \ 'linter_warn': 'lightline#ale#warnings', - \ 'linter_errors': 'lightline#ale#errors', - \ 'linter_ok': 'lightline#ale#ok', - \ } - -" How to color custom components -let g:lightline.component_type={ - \ 'readonly': 'error', - \ 'linter_checking': 'left', - \ 'linter_warn': 'warning', - \ 'linter_errors': 'error', - \ 'linter_ok': 'left', - \ } - -" Show pretty icons instead of text for linting status -let g:lightline#ale#indicator_checking='â³' -let g:lightline#ale#indicator_warnings='â—†' -let g:lightline#ale#indicator_errors='✗' -let g:lightline#ale#indicator_ok='✓' - -" Show a lock icon when editing a read-only file when it makes sense -function! LightlineReadonly() - return &ft!~?'help\|vimfiler\|netrw' && &readonly ? 'î‚¢' : '' -endfunction - -" Show a '+' when the buffer is modified, '-' if not, when it makes sense -function! LightlineModified() - return &ft=~'help\|vimfiler\|netrw' ? '' : &modified ? '+' : &modifiable ? '' : '-' -endfunction - -" Show branch name with nice icon in status line, when it makes sense -function! LightlineFugitive() - if &ft!~?'help\|vimfiler\|netrw' && exists('*fugitive#head') - let branch=fugitive#head() - return branch!=#'' ? 'î‚  '.branch : '' - endif - return '' -endfunction diff --git a/home/vim/plugin/settings/lspconfig.vim b/home/vim/plugin/settings/lspconfig.vim new file mode 100644 index 0000000..dc706cc --- /dev/null +++ b/home/vim/plugin/settings/lspconfig.vim @@ -0,0 +1,41 @@ +lua << EOF +local lspconfig = require("lspconfig") +local lsp = require("ambroisie.lsp") +local utils = require("ambroisie.utils") + +-- Inform servers we are able to do completion, snippets, etc... +local capabilities = vim.lsp.protocol.make_client_capabilities() +capabilities = require("cmp_nvim_lsp").update_capabilities(capabilities) + +-- C/C++ +if utils.is_executable("clangd") then + lspconfig.clangd.setup({ + capabilities = capabilities, + on_attach = lsp.on_attach, + }) +end + +-- Nix +if utils.is_executable("rnix-lsp") then + lspconfig.rnix.setup({ + capabilities = capabilities, + on_attach = lsp.on_attach, + }) +end + +-- Python +if utils.is_executable("pyright") then + lspconfig.pyright.setup({ + capabilities = capabilities, + on_attach = lsp.on_attach, + }) +end + +-- Rust +if utils.is_executable("rust-analyzer") then + lspconfig.rust_analyzer.setup({ + capabilities = capabilities, + on_attach = lsp.on_attach, + }) +end +EOF diff --git a/home/vim/plugin/settings/lualine.vim b/home/vim/plugin/settings/lualine.vim new file mode 100644 index 0000000..93c38bb --- /dev/null +++ b/home/vim/plugin/settings/lualine.vim @@ -0,0 +1,63 @@ +lua << EOF +local lualine = require("lualine") +local utils = require("ambroisie.utils") + +local function list_spell_languages() + if not vim.opt.spell:get() then + return "" + end + + return table.concat(vim.opt.spelllang:get(), ", ") +end + +local function list_lsp_clients() + local client_names = utils.list_lsp_clients() + + if #client_names == 0 then + return "" + end + + return "[ " .. table.concat(client_names, " ") .. " ]" +end + +lualine.setup({ + options = { + icons_enabled = false, + section_separators = "", + component_separators = "|", + }, + sections = { + lualine_a = { + { "mode" }, + }, + lualine_b = { + { "FugitiveHead" }, + { "filename", symbols = { readonly = "🔒" } }, + }, + lualine_c = { + { list_spell_languages }, + { "lsp_progress" }, + }, + lualine_x = { + { list_lsp_clients }, + { + "diagnostics", + -- Only use the diagnostics API + sources = { "nvim_diagnostic" }, + }, + }, + lualine_y = { + { "fileformat" }, + { "encoding" }, + { "filetype" }, + }, + lualine_z = { + "location", + }, + }, + extensions = { + "fugitive", + "quickfix", + }, +}) +EOF diff --git a/home/vim/plugin/settings/luasnip.vim b/home/vim/plugin/settings/luasnip.vim new file mode 100644 index 0000000..9527d22 --- /dev/null +++ b/home/vim/plugin/settings/luasnip.vim @@ -0,0 +1,3 @@ +lua << EOF +require("luasnip.loaders.from_vscode").load() +EOF diff --git a/home/vim/plugin/settings/null-ls.vim b/home/vim/plugin/settings/null-ls.vim new file mode 100644 index 0000000..b2102bd --- /dev/null +++ b/home/vim/plugin/settings/null-ls.vim @@ -0,0 +1,125 @@ +lua << EOF +local null_ls = require("null-ls") +local lsp = require("ambroisie.lsp") +local utils = require("ambroisie.utils") + +null_ls.setup({ + on_attach = lsp.on_attach, +}) + +-- C, C++ +null_ls.register({ + null_ls.builtins.formatting.clang_format.with({ + -- Only used if available, but prefer clangd formatting if available + condition = function() + return utils.is_executable("clang-format") and not utils.is_executable("clangd") + end, + }), +}) + +-- Haskell +null_ls.register({ + null_ls.builtins.formatting.brittany.with({ + -- Only used if available + condition = utils.is_executable_condition("brittany"), + }), +}) + +-- Nix +null_ls.register({ + null_ls.builtins.formatting.nixpkgs_fmt.with({ + -- Only used if available, but prefer rnix if available + condition = function() + return utils.is_executable("nixpkgs-fmt") and not utils.is_executable("rnix-lsp") + end, + }), +}) + +-- Python +null_ls.register({ + null_ls.builtins.diagnostics.flake8.with({ + -- Only used if available, but prefer pflake8 if available + condition = function() + return utils.is_executable("flake8") and not utils.is_executable("pflake8") + end, + }), + null_ls.builtins.diagnostics.pyproject_flake8.with({ + -- Only used if available + condition = utils.is_executable_condition("pflake8"), + }), + null_ls.builtins.diagnostics.mypy.with({ + -- Only used if available + condition = utils.is_executable_condition("mypy"), + }), + null_ls.builtins.diagnostics.pylint.with({ + -- Only used if available + condition = utils.is_executable_condition("pylint"), + }), + null_ls.builtins.formatting.black.with({ + extra_args = { "--fast" }, + -- Only used if available + condition = utils.is_executable_condition("black"), + }), + null_ls.builtins.formatting.isort.with({ + -- Only used if available + condition = utils.is_executable_condition("isort"), + }), +}) + + +-- Shell (non-POSIX) +null_ls.register({ + null_ls.builtins.code_actions.shellcheck.with({ + -- Restrict to bash and zsh + filetypes = { "bash", "zsh" }, + -- Only used if available + condition = utils.is_executable_condition("shellcheck"), + }), + null_ls.builtins.diagnostics.shellcheck.with({ + -- Show error code in message + diagnostics_format = "[#{c}] #{m}", + -- Require explicit empty string test, use bash dialect + extra_args = { "-s", "bash", "-o", "avoid-nullary-conditions" }, + -- Restrict to bash and zsh + filetypes = { "bash", "zsh" }, + -- Only used if available + condition = utils.is_executable_condition("shellcheck"), + }), + null_ls.builtins.formatting.shfmt.with({ + -- Indent with 4 spaces, simplify the code, indent switch cases, + -- add space after redirection, use bash dialect + extra_args = { "-i", "4", "-s", "-ci", "-sr", "-ln", "bash" }, + -- Restrict to bash and zsh + filetypes = { "bash", "zsh" }, + -- Only used if available + condition = utils.is_executable_condition("shfmt"), + }), +}) + +-- Shell (POSIX) +null_ls.register({ + null_ls.builtins.code_actions.shellcheck.with({ + -- Restrict to POSIX sh + filetypes = { "sh" }, + -- Only used if available + condition = utils.is_executable_condition("shellcheck"), + }), + null_ls.builtins.diagnostics.shellcheck.with({ + -- Show error code in message + diagnostics_format = "[#{c}] #{m}", + -- Require explicit empty string test + extra_args = { "-o", "avoid-nullary-conditions" }, + -- Restrict to POSIX sh + filetypes = { "sh" }, + -- Only used if available + condition = utils.is_executable_condition("shellcheck"), + }), + null_ls.builtins.formatting.shfmt.with({ + -- Indent with 4 spaces, simplify the code, indent switch cases, + -- add space after redirection, use POSIX + extra_args = { "-i", "4", "-s", "-ci", "-sr", "-ln", "posix" }, + -- Only used if available + condition = utils.is_executable_condition("shfmt"), + }), +}) +EOF diff --git a/home/vim/plugin/settings/surround.vim b/home/vim/plugin/settings/surround.vim new file mode 100644 index 0000000..e5eb81b --- /dev/null +++ b/home/vim/plugin/settings/surround.vim @@ -0,0 +1,5 @@ +lua << EOF +require("nvim-surround").setup({ + -- No configuration at the moment +}) +EOF diff --git a/home/vim/plugin/settings/telescope.vim b/home/vim/plugin/settings/telescope.vim new file mode 100644 index 0000000..fc2c5fb --- /dev/null +++ b/home/vim/plugin/settings/telescope.vim @@ -0,0 +1,26 @@ +lua << EOF +local telescope = require("telescope") + +telescope.setup({ + defaults = { + mappings = { + i = { + [""] = "which_key", + -- I want the normal readline mappings rather than scrolling + [""] = false, + } + } + }, + extensions = { + fzf = { + fuzzy = true, + override_generic_sorter = true, + override_file_sorter = true, + case_mode = "smart_case", + }, + }, +}) + +telescope.load_extension("fzf") +telescope.load_extension("lsp_handlers") +EOF diff --git a/home/vim/plugin/settings/tree-sitter.vim b/home/vim/plugin/settings/tree-sitter.vim new file mode 100644 index 0000000..1204185 --- /dev/null +++ b/home/vim/plugin/settings/tree-sitter.vim @@ -0,0 +1,58 @@ +lua << EOF +local ts_config = require("nvim-treesitter.configs") +ts_config.setup({ + highlight = { + enable = true, + -- Avoid duplicate highlighting + additional_vim_regex_highlighting = false, + }, + indent = { + enable = true, + }, + context_commentstring = { + enable = true, + }, + textobjects = { + select = { + enable = true, + -- Jump to matching text objects + lookahead = true, + keymaps = { + ["aa"] = "@parameter.outer", + ["ia"] = "@parameter.inner", + ["ab"] = "@block.outer", + ["ib"] = "@block.inner", + ["ac"] = "@class.outer", + ["ic"] = "@class.inner", + ["af"] = "@function.outer", + ["if"] = "@function.inner", + ["ak"] = "@comment.outer", + ["aS"] = "@statement.outer", + }, + }, + move = { + enable = true, + -- Add to jump list + set_jumps = true, + goto_next_start = { + ["]m"] = "@function.outer", + ["]S"] = "@statement.outer", + ["]]"] = "@class.outer", + }, + goto_next_end = { + ["]M"] = "@function.outer", + ["]["] = "@class.outer", + }, + goto_previous_start = { + ["[m"] = "@function.outer", + ["[S"] = "@statement.outer", + ["[["] = "@class.outer", + }, + goto_previous_end = { + ["[M"] = "@function.outer", + ["[]"] = "@class.outer", + }, + }, + }, +}) +EOF diff --git a/home/vim/plugin/settings/which-key.vim b/home/vim/plugin/settings/which-key.vim new file mode 100644 index 0000000..505bdc4 --- /dev/null +++ b/home/vim/plugin/settings/which-key.vim @@ -0,0 +1,4 @@ +lua << EOF +local wk = require("which-key") +wk.setup() +EOF diff --git a/home/wm/cursor/default.nix b/home/wm/cursor/default.nix new file mode 100644 index 0000000..9426232 --- /dev/null +++ b/home/wm/cursor/default.nix @@ -0,0 +1,23 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.home.wm.cursor; + + cfg_x = config.my.home.x; + cfg_gtk = config.my.home.gtk; +in +{ + config = lib.mkIf cfg.enable { + home.pointerCursor = { + package = pkgs.ambroisie.vimix-cursors; + name = "Vimix-cursors"; + + x11 = { + inherit (cfg_x) enable; + }; + + gtk = { + inherit (cfg_gtk) enable; + }; + }; + }; +} diff --git a/home/wm/default.nix b/home/wm/default.nix new file mode 100644 index 0000000..fb9ecee --- /dev/null +++ b/home/wm/default.nix @@ -0,0 +1,92 @@ +{ config, lib, pkgs, ... }: +let + mkRelatedOption = description: relatedWMs: + let + isActivatedWm = wm: config.my.home.wm.windowManager == wm; + in + (lib.mkEnableOption description) // { + default = builtins.any isActivatedWm relatedWMs; + }; +in +{ + imports = [ + ./cursor + ./dunst + ./i3 + ./i3bar + ./rofi + ./screen-lock + ]; + + options.my.home.wm = with lib; { + windowManager = mkOption { + type = with types; nullOr (enum [ "i3" ]); + default = null; + example = "i3"; + description = "Which window manager to use for home session"; + }; + + cursor = { + enable = mkRelatedOption "dunst configuration" [ "i3" ]; + }; + + dunst = { + enable = mkRelatedOption "dunst configuration" [ "i3" ]; + }; + + i3bar = { + enable = mkRelatedOption "i3bar configuration" [ "i3" ]; + }; + + rofi = { + enable = mkRelatedOption "rofi menu" [ "i3" ]; + }; + + screen-lock = { + enable = mkRelatedOption "automatic X screen locker" [ "i3" ]; + + command = mkOption { + type = types.str; + default = "${pkgs.i3lock}/bin/i3lock -n -c 000000"; + example = "\${pkgs.i3lock}/bin/i3lock -n -i lock.png"; + description = "Locker command to run"; + }; + + cornerLock = { + enable = my.mkDisableOption '' + Move mouse to upper-left corner to lock instantly, lower-right corner to + disable auto-lock. + ''; + + delay = mkOption { + type = types.int; + default = 5; + example = 15; + description = "How many seconds before locking this way"; + }; + }; + + notify = { + enable = my.mkDisableOption "Notify when about to lock the screen"; + + delay = mkOption { + type = types.int; + default = 5; + example = 15; + description = '' + How many seconds in advance should there be a notification. + This value must be at lesser than or equal to `cornerLock.delay` + when both options are enabled. + ''; + }; + }; + + timeout = mkOption { + type = types.ints.between 1 60; + default = 15; + example = 1; + description = "Inactive time interval to lock the screen automatically"; + }; + }; + }; +} diff --git a/home/wm/dunst/default.nix b/home/wm/dunst/default.nix new file mode 100644 index 0000000..949db32 --- /dev/null +++ b/home/wm/dunst/default.nix @@ -0,0 +1,73 @@ +{ config, lib, ... }: +let + cfg = config.my.home.wm.dunst; +in +{ + config = lib.mkIf cfg.enable { + services.dunst = { + enable = true; + + settings = { + global = { + alignment = "center"; # Put message in the middle of the box + browser = "xdg-open"; # use default browser to open links + dmenu = + lib.mkIf + config.my.home.wm.rofi.enable + "rofi -p dunst -dmenu"; # use rofi for menu + follow = "keyboard"; # follow keyboard focus + font = "Monospace 8"; # Simple looking font + frame_width = 3; # small frame + markup = "full"; # subset of HTML + max_icon_size = 32; # avoid icons that are too big + padding = 6; # distance between text and bubble border + progress_bar = true; # show a progress bar in notification bubbles + separator_color = "frame"; # use frame color to separate bubbles + sort = true; # sort messages by urgency + word_wrap = true; # Break long lines to make them readable + + # Fixed size notifications, slightly recessed from the top right + width = 300; + height = 50; + origin = "top-right"; + offset = "15x50"; + }; + + urgency_low = { + background = "#191311"; + foreground = "#3b7c87"; + frame_color = "#3b7c87"; + highlight = "#4998a6"; + timeout = 10; + }; + + urgency_normal = { + background = "#191311"; + foreground = "#5b8234"; + frame_color = "#5b8234"; + highlight = "#73a542"; + timeout = 10; + }; + + urgency_critical = { + background = "#191311"; + foreground = "#b7472a"; + frame_color = "#b7472a"; + highlight = "#d25637"; + timeout = 0; + }; + + fullscreen_delay_everything = { + # delay notifications by default + fullscreen = "delay"; + }; + + fullscreen_show_critical = { + # show critical notification + fullscreen = "show"; + msg_urgency = "critical"; + }; + }; + }; + }; +} diff --git a/home/wm/i3/default.nix b/home/wm/i3/default.nix new file mode 100644 index 0000000..57235ef --- /dev/null +++ b/home/wm/i3/default.nix @@ -0,0 +1,386 @@ +{ config, lib, pkgs, ... }: +let + isEnabled = config.my.home.wm.windowManager == "i3"; + + terminal = + if config.my.home.terminal.program != null + then config.my.home.terminal.program + else "i3-sensible-terminal"; + + alt = "Mod1"; # `Alt` key + modifier = "Mod4"; # `Super` key + movementKeys = [ "Left" "Down" "Up" "Right" ]; + vimMovementKeys = [ "h" "j" "k" "l" ]; + shutdownMode = + "(l)ock, (e)xit, switch_(u)ser, (h)ibernate, (r)eboot, (Shift+s)hutdown"; + + # Takes an attrset of bindings for movement keys, transforms it to Vim keys + toVimKeyBindings = + let + toVimKeys = builtins.replaceStrings movementKeys vimMovementKeys; + in + lib.my.renameAttrs toVimKeys; + + # Takes an attrset of bindings for movement keys, add equivalent Vim keys + addVimKeyBindings = bindings: bindings // (toVimKeyBindings bindings); + # Generate an attrset of movement bindings, using the mapper function + genMovementBindings = f: addVimKeyBindings (lib.my.genAttrs' movementKeys f); + + # Used in multiple scripts to show messages through keybindings + notify-send = "${pkgs.libnotify}/bin/notify-send"; + + # Screen backlight management + changeBacklight = "${pkgs.ambroisie.change-backlight}/bin/change-backlight"; + + # Audio and volume management + changeAudio = "${pkgs.ambroisie.change-audio}/bin/change-audio"; + + # Lock management + toggleXautolock = + let + systemctlUser = "${pkgs.systemd}/bin/systemctl --user"; + notify = "${notify-send} -u low" + + " -h string:x-canonical-private-synchronous:xautolock-toggle"; + in + pkgs.writeScript "toggle-xautolock" '' + #!/bin/sh + if ${systemctlUser} is-active xautolock-session.service; then + ${systemctlUser} stop --user xautolock-session.service + xset s off + ${notify} "Disabled Xautolock" + else + ${systemctlUser} start xautolock-session.service + xset s on + ${notify} "Enabled Xautolock" + fi + ''; +in +{ + config = lib.mkIf isEnabled { + home.packages = with pkgs; [ + ambroisie.dragger # drag-and-drop from the CLI + ambroisie.i3-get-window-criteria # little helper for i3 configuration + arandr # Used by a mapping + pamixer # Used by a mapping + playerctl # Used by a mapping + ]; + + xsession.windowManager.i3 = { + enable = true; + + config = { + inherit modifier; + + bars = + let + barConfigPath = + config.xdg.configFile."i3status-rust/config-top.toml".target; + i3status-rs = + "${config.programs.i3status-rust.package}/bin/i3status-rs"; + in + [ + { + statusCommand = "${i3status-rs} ${barConfigPath}"; + trayOutput = "primary"; + position = "top"; + + colors = { + background = "#021215"; + statusline = "#93a1a1"; + separator = "#2aa198"; + + focusedWorkspace = { + border = "#2aa198"; + background = "#073642"; + text = "#eee895"; + }; + + activeWorkspace = { + border = "#073642"; + background = "#002b36"; + text = "#839496"; + }; + + inactiveWorkspace = { + border = "#002b36"; + background = "#021215"; + text = "#586e75"; + }; + + urgentWorkspace = { + border = "#cb4b16"; + background = "#dc322f"; + text = "#fdf6e3"; + }; + }; + + fonts = { + names = [ "DejaVu Sans Mono" "FontAwesome5Free" ]; + size = 8.0; + }; + } + ]; + + floating = { + inherit modifier; + + criteria = [ + { class = "^tridactyl_editor$"; } + { class = "^Blueman-.*$"; } + { title = "^htop$"; } + { class = "^Thunderbird$"; instance = "Mailnews"; window_role = "filterlist"; } + { class = "^Pavucontrol.*$"; } + { class = "^Arandr$"; } + ]; + }; + + focus = { + followMouse = true; # It is annoying sometimes, but useful enough to use + mouseWarping = true; # Let's moving around when switching screens + }; + + fonts = { + names = [ "DejaVu Sans Mono" ]; + size = 8.0; + }; + + # I don't care for i3's default values, I specify them all explicitly + keybindings = lib.my.recursiveMerge [ + { + # The basics + "${modifier}+Return" = "exec ${terminal} ${ + lib.optionalString config.my.home.tmux.enable "-e tmux new-session" + }"; + "${modifier}+Shift+Return" = "exec env TMUX=nil ${terminal}"; + "${modifier}+Shift+q" = "kill"; + "${modifier}+f" = "fullscreen toggle"; + "${modifier}+Shift+c" = "reload"; + "${modifier}+Shift+r" = "restart"; + "${modifier}+Shift+e" = + "exec i3-nagbar -t warning -m 'Do you want to exit i3?' -b 'Yes' 'i3-msg exit'"; + } + { + # Splits + "${modifier}+g" = "split h"; # Horizontally + "${modifier}+v" = "split v"; # Vertically + } + { + # Layouts + "${modifier}+s" = "layout stacking"; + "${modifier}+w" = "layout tabbed"; + "${modifier}+e" = "layout toggle split"; + } + { + # Toggle tiling/floating + "${modifier}+Control+space" = "floating toggle"; + # Change focus between tiling/floating + "${modifier}+space" = "focus mode_toggle"; + } + { + # Focus parent container + "${modifier}+q" = "focus parent"; + # Focus child container + "${modifier}+a" = "focus child"; + } + (lib.optionalAttrs config.my.home.wm.rofi.enable { + # Rofi tools + "${modifier}+d" = "exec rofi -show drun -disable-history"; + "${modifier}+Shift+d" = "exec rofi -show run -disable-history"; + "${modifier}+p" = "exec --no-startup-id flameshot gui"; + "${modifier}+Shift+p" = "exec rofi -show emoji"; + "${modifier}+b" = + let + inherit (config.my.home.bluetooth) enable; + prog = "${pkgs.ambroisie.rofi-bluetooth}/bin/rofi-bluetooth"; + in + lib.mkIf enable "exec ${prog}"; + }) + ( + # Changing container focus + genMovementBindings ( + key: lib.nameValuePair + "${modifier}+${key}" + "focus ${lib.toLower key}" + ) + ) + ( + # Changing screen focus + genMovementBindings ( + key: lib.nameValuePair + "${modifier}+${alt}+${key}" + "focus output ${lib.toLower key}" + ) + ) + ( + # Moving workspace to another screen + genMovementBindings ( + key: lib.nameValuePair + "${modifier}+${alt}+Control+${key}" + "move workspace to output ${lib.toLower key}" + ) + ) + ( + # Moving container to another screen + genMovementBindings ( + key: lib.nameValuePair + "${modifier}+${alt}+Shift+${key}" + "move container to output ${lib.toLower key}" + ) + ) + (addVimKeyBindings { + # Scroll through workspaces on given screen + "${modifier}+Control+Left" = "workspace prev_on_output"; + "${modifier}+Control+Right" = "workspace next_on_output"; + # Use scratchpad + "${modifier}+Control+Up" = "move to scratchpad"; + "${modifier}+Control+Down" = "scratchpad show"; + }) + ( + # Moving floating window + genMovementBindings ( + key: lib.nameValuePair + "${modifier}+Shift+${key}" + "move ${lib.toLower key} 10 px" + ) + ) + { + # Media keys + "XF86AudioRaiseVolume" = "exec --no-startup-id ${changeAudio} up 5"; + "XF86AudioLowerVolume" = "exec --no-startup-id ${changeAudio} down 5"; + "Control+XF86AudioRaiseVolume" = "exec --no-startup-id ${changeAudio} up 1"; + "Control+XF86AudioLowerVolume" = "exec --no-startup-id ${changeAudio} down 1"; + + "Shift+XF86AudioRaiseVolume" = "exec --no-startup-id ${changeAudio} up --force 5"; + "Shift+XF86AudioLowerVolume" = "exec --no-startup-id ${changeAudio} down --force 5"; + "Control+Shift+XF86AudioRaiseVolume" = "exec --no-startup-id ${changeAudio} up --force 1"; + "Control+Shift+XF86AudioLowerVolume" = "exec --no-startup-id ${changeAudio} down --force 1"; + + "XF86AudioMute" = "exec --no-startup-id ${changeAudio} toggle"; + "XF86AudioMicMute" = "exec --no-startup-id ${changeAudio} toggle mic"; + + "XF86AudioPlay" = "exec playerctl play-pause"; + "XF86AudioNext" = "exec playerctl next"; + "XF86AudioPrev" = "exec playerctl previous"; + } + { + # Screen management + "XF86Display" = "exec arandr"; + "XF86MonBrightnessUp" = "exec --no-startup-id ${changeBacklight} up 10"; + "XF86MonBrightnessDown" = "exec --no-startup-id ${changeBacklight} down 10"; + "Control+XF86MonBrightnessUp" = "exec --no-startup-id ${changeBacklight} up 1"; + "Control+XF86MonBrightnessDown" = "exec --no-startup-id ${changeBacklight} down 1"; + } + { + # Sub-modes + "${modifier}+r" = "mode resize"; + "${modifier}+Shift+space" = "mode floating"; + } + (lib.optionalAttrs config.my.home.wm.screen-lock.enable { + "${modifier}+x" = "exec ${toggleXautolock}"; + }) + ( + let + execDunstctl = "exec ${pkgs.dunst}/bin/dunstctl"; + in + lib.optionalAttrs config.my.home.wm.dunst.enable { + "${modifier}+minus" = "${execDunstctl} close"; + "${modifier}+Shift+minus" = "${execDunstctl} close-all"; + "${modifier}+equal" = "${execDunstctl} history-pop"; + } + ) + ]; + + keycodebindings = + let + toKeycode = n: if n == 0 then 19 else n + 9; + createWorkspaceBindings = mapping: command: + let + createWorkspaceBinding = num: + lib.nameValuePair + "${mapping}+${toString (toKeycode num)}" + "${command} ${toString num}"; + oneToNine = builtins.genList (x: x + 1) 9; + in + lib.my.genAttrs' oneToNine createWorkspaceBinding; + in + lib.my.recursiveMerge [ + (createWorkspaceBindings modifier "workspace number") + (createWorkspaceBindings "${modifier}+Shift" "move container to workspace number") + { + "${modifier}+${toString (toKeycode 0)}" = ''mode "${shutdownMode}"''; + } + ]; + + modes = + let + makeModeBindings = attrs: (addVimKeyBindings attrs) // { + "Escape" = "mode default"; + "Return" = "mode default"; + }; + in + { + resize = makeModeBindings { + # Normal movements + "Left" = "resize shrink width 10 px or 10 ppt"; + "Down" = "resize grow height 10 px or 10 ppt"; + "Up" = "resize shrink height 10 px or 10 ppt"; + "Right" = "resize grow width 10 px or 10 ppt"; + # Small movements + "Control+Left" = "resize shrink width 1 px or 1 ppt"; + "Control+Down" = "resize grow height 1 px or 1 ppt"; + "Control+Up" = "resize shrink height 1 px or 1 ppt"; + "Control+Right" = "resize grow width 1 px or 1 ppt"; + # Big movements + "Shift+Left" = "resize shrink width 100 px or 100 ppt"; + "Shift+Down" = "resize grow height 100 px or 100 ppt"; + "Shift+Up" = "resize shrink height 100 px or 100 ppt"; + "Shift+Right" = "resize grow width 100 px or 100 ppt"; + }; + + floating = makeModeBindings { + # Normal movements + "Left" = "move left 10 px"; + "Down" = "move down 10 px"; + "Up" = "move up 10 px"; + "Right" = "move right 10 px"; + # Small movements + "Control+Left" = "move left 1 px"; + "Control+Down" = "move down 1 px"; + "Control+Up" = "move up 1 px"; + "Control+Right" = "move right 1 px"; + # Big movements + "Shift+Left" = "move left 100 px"; + "Shift+Down" = "move down 100 px"; + "Shift+Up" = "move up 100 px"; + "Shift+Right" = "move right 100 px"; + }; + + ${shutdownMode} = makeModeBindings { + "l" = "exec --no-startup-id loginctl lock-session, mode default"; + "s" = "exec --no-startup-id systemctl suspend, mode default"; + "u" = "exec --no-startup-id dm-tool switch-to-greeter, mode default"; + "e" = "exec --no-startup-id i3-msg exit, mode default"; + "h" = "exec --no-startup-id systemctl hibernate, mode default"; + "r" = "exec --no-startup-id systemctl reboot, mode default"; + "Shift+s" = "exec --no-startup-id systemctl poweroff, mode default"; + }; + }; + + startup = [ + # FIXME + # { commdand; always; notification; } + ]; + + window = { + commands = [ + # Make htop window bigger + { + criteria = { title = "^htop$"; }; + command = "resize set 80 ppt 80 ppt, move position center"; + } + ]; + }; + }; + }; + }; +} diff --git a/home/wm/i3bar/default.nix b/home/wm/i3bar/default.nix new file mode 100644 index 0000000..a330134 --- /dev/null +++ b/home/wm/i3bar/default.nix @@ -0,0 +1,87 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.home.wm.i3bar; +in +{ + config = lib.mkIf cfg.enable { + home.packages = with pkgs; [ + alsaUtils # Used by `sound` block + lm_sensors # Used by `temperature` block + font-awesome # Icon font + ]; + + programs.i3status-rust = { + enable = true; + + bars = { + top = { + icons = "awesome5"; + + blocks = builtins.filter (attr: attr != { }) [ + { + block = "music"; + buttons = [ "prev" "play" "next" ]; + max_width = 50; + dynamic_width = true; + hide_when_empty = true; + } + (lib.optionalAttrs config.my.home.bluetooth.enable { + block = "bluetooth"; + mac = "4C:87:5D:06:40:D9"; + hide_disconnected = true; + format = "Boson {percentage}"; + }) + (lib.optionalAttrs config.my.home.bluetooth.enable { + block = "bluetooth"; + mac = "94:DB:56:00:EE:93"; + hide_disconnected = true; + format = "Protons {percentage}"; + }) + (lib.optionalAttrs config.my.home.bluetooth.enable { + block = "bluetooth"; + mac = "F7:78:BA:76:52:F7"; + hide_disconnected = true; + format = "MX Ergo {percentage}"; + }) + { + block = "cpu"; + } + { + block = "disk_space"; + } + { + block = "net"; + format = "{ssid} {ip} {signal_strength}"; + } + { + block = "backlight"; + invert_icons = true; + } + { + block = "battery"; + format = "{percentage} ({time})"; + full_format = "{percentage}"; + } + { + block = "temperature"; + collapsed = false; + } + { + block = "sound"; + device_kind = "source"; # Microphone status + format = ""; # Only show icon + } + { + block = "sound"; + show_volume_when_muted = true; + } + { + block = "time"; + format = "%F %T"; + } + ]; + }; + }; + }; + }; +} diff --git a/home/wm/rofi/default.nix b/home/wm/rofi/default.nix new file mode 100644 index 0000000..9707ed7 --- /dev/null +++ b/home/wm/rofi/default.nix @@ -0,0 +1,21 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.home.wm.rofi; +in +{ + config = lib.mkIf cfg.enable { + programs.rofi = { + enable = true; + + terminal = config.my.home.terminal.program; # null by default + + package = pkgs.rofi.override { + plugins = with pkgs; [ + rofi-emoji + ]; + }; + + theme = "gruvbox-dark-hard"; + }; + }; +} diff --git a/home/wm/screen-lock/default.nix b/home/wm/screen-lock/default.nix new file mode 100644 index 0000000..95060b8 --- /dev/null +++ b/home/wm/screen-lock/default.nix @@ -0,0 +1,56 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.home.wm.screen-lock; + + notficationCmd = + let + duration = toString (cfg.notify.delay * 1000); + notifyCmd = "${pkgs.libnotify}/bin/notify-send -u critical -t ${duration}"; + in + # Needs to be surrounded by quotes for systemd to launch it correctly + ''"${notifyCmd} -- 'Locking in ${toString cfg.notify.delay} seconds'"''; +in +{ + config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = + let + inherit (cfg) cornerLock notify; + bothEnabled = cornerLock.enable && notify.enable; + cornerLockHigherThanNotify = cornerLock.delay >= notify.delay; + in + bothEnabled -> cornerLockHigherThanNotify; + message = '' + `config.my.home.wm.notify.delay` cannot have a value higher than + `config.my.home.wm.cornerLock.delay`. + ''; + } + ]; + + services.screen-locker = { + enable = true; + + inactiveInterval = cfg.timeout; + + lockCmd = cfg.command; + + xautolock = { + extraOptions = lib.optionals cfg.cornerLock.enable [ + # Mouse corners: instant lock on upper-left, never lock on lower-right + "-cornerdelay" + "${toString cfg.cornerLock.delay}" + "-cornerredelay" + "${toString cfg.cornerLock.delay}" + "-corners" + "+00-" + ] ++ lib.optionals cfg.notify.enable [ + "-notify" + "${toString cfg.notify.delay}" + "-notifier" + notficationCmd + ]; + }; + }; + }; +} diff --git a/home/x/default.nix b/home/x/default.nix new file mode 100644 index 0000000..0312bc4 --- /dev/null +++ b/home/x/default.nix @@ -0,0 +1,21 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.home.x; +in +{ + imports = [ + ./keyboard + ]; + + options.my.home.x = with lib; { + enable = mkEnableOption "X server configuration"; + }; + + config = lib.mkIf cfg.enable { + xsession.enable = true; + + home.packages = with pkgs; [ + xsel + ]; + }; +} diff --git a/home/x/keyboard/default.nix b/home/x/keyboard/default.nix new file mode 100644 index 0000000..40af800 --- /dev/null +++ b/home/x/keyboard/default.nix @@ -0,0 +1,12 @@ +{ config, lib, ... }: +let + cfg = config.my.home.x; +in +{ + config = lib.mkIf cfg.enable { + home.keyboard = { + layout = "fr"; + variant = "us"; + }; + }; +} diff --git a/home/xdg.nix b/home/xdg/default.nix similarity index 93% rename from home/xdg.nix rename to home/xdg/default.nix index bdeb326..b335842 100644 --- a/home/xdg.nix +++ b/home/xdg/default.nix @@ -31,6 +31,7 @@ in # A tidy home is a tidy mind dataFile = { "bash/.keep".text = ""; + "gdb/.keep".text = ""; "tig/.keep".text = ""; }; }; @@ -39,10 +40,10 @@ in config.home.sessionVariables = with config.xdg; lib.mkIf cfg.enable { CARGO_HOME = "${dataHome}/cargo"; DOCKER_CONFIG = "${configHome}/docker"; + GDBHISTFILE = "${dataHome}/gdb/gdb_history"; HISTFILE = "${dataHome}/bash/history"; INPUTRC = "${configHome}/readline/inputrc"; LESSHISTFILE = "${dataHome}/less/history"; LESSKEY = "${configHome}/less/lesskey"; - WGETRC = "${configHome}/wgetrc"; }; } diff --git a/home/zathura/default.nix b/home/zathura/default.nix new file mode 100644 index 0000000..6162542 --- /dev/null +++ b/home/zathura/default.nix @@ -0,0 +1,20 @@ +{ config, lib, ... }: +let + cfg = config.my.home.zathura; +in +{ + options.my.home.zathura = with lib; { + enable = mkEnableOption "zathura configuration"; + }; + + config.programs.zathura = lib.mkIf cfg.enable { + enable = true; + + options = { + # Show '~' instead of full path to '$HOME' in window title + "window-title-home-tilde" = true; + # Show '~' instead of full path to '$HOME' in status bar + "statusbar-home-tilde" = true; + }; + }; +} diff --git a/home/zsh/default.nix b/home/zsh/default.nix index b7b9e8e..4d61685 100644 --- a/home/zsh/default.nix +++ b/home/zsh/default.nix @@ -7,73 +7,89 @@ in enable = mkDisableOption "zsh configuration"; }; - config.programs.zsh = lib.mkIf cfg.enable { - enable = true; - dotDir = ".config/zsh"; # Don't clutter $HOME - enableCompletion = true; - - history = { - size = 50000; - ignoreSpace = true; - ignoreDups = true; - share = true; - path = "${config.xdg.dataHome}/zsh/zsh_history"; - }; - - plugins = with pkgs; [ - { - name = "fast-syntax-highlighting"; - src = fetchFromGitHub { - owner = "zdharma"; - repo = "fast-syntax-highlighting"; - rev = "v1.55"; - sha256 = "sha256-DWVFBoICroKaKgByLmDEo4O+xo6eA8YO792g8t8R7kA="; - }; - } - { - name = "agkozak-zsh-prompt"; - src = fetchFromGitHub { - owner = "agkozak"; - repo = "agkozak-zsh-prompt"; - rev = "v3.9.0"; - sha256 = "sha256-VTRL+8ph2eI7iPht15epkLggAgtLGxB3DORFTW5GrhE="; - }; - } + config = lib.mkIf cfg.enable { + home.packages = with pkgs; [ + zsh-completions ]; - # Modal editing is life, but CLI benefits from emacs gymnastics - defaultKeymap = "emacs"; + programs.zsh = { + enable = true; + dotDir = ".config/zsh"; # Don't clutter $HOME + enableCompletion = true; - initExtra = lib.concatMapStrings builtins.readFile [ - ./completion-styles.zsh - ./extra-mappings.zsh - ./options.zsh - ]; + history = { + size = 500000; + save = 500000; + extended = false; + ignoreSpace = true; + ignoreDups = true; + share = false; + path = "${config.xdg.dataHome}/zsh/zsh_history"; + }; - localVariables = { - # I like having the full path - AGKOZAK_PROMPT_DIRTRIM = 0; - # Because I *am* from EPITA - AGKOZAK_PROMPT_CHAR = [ "42sh$" "42sh#" ":" ]; - # Easy on the eyes - AGKOZAK_COLORS_BRANCH_STATUS = "magenta"; - # I don't like moving my eyes - AGKOZAK_LEFT_PROMPT_ONLY = 1; + plugins = with pkgs; [ + { + name = "fast-syntax-highlighting"; + file = "share/zsh/site-functions/fast-syntax-highlighting.plugin.zsh"; + src = pkgs.zsh-fast-syntax-highlighting; + } + { + name = "agkozak-zsh-prompt"; + file = "share/zsh/site-functions/agkozak-zsh-prompt.plugin.zsh"; + src = pkgs.agkozak-zsh-prompt; + } + ]; + + # Modal editing is life, but CLI benefits from emacs gymnastics + defaultKeymap = "emacs"; + + # Make those happen early to avoid doing double the work + initExtraFirst = + lib.optionalString config.my.home.tmux.enable '' + # Launch tmux unless already inside one + if [ -z "$TMUX" ]; then + exec tmux new-session + fi + '' + ; + + initExtra = lib.concatMapStrings builtins.readFile [ + ./completion-styles.zsh + ./extra-mappings.zsh + ./options.zsh + ]; + + localVariables = { + # I like having the full path + AGKOZAK_PROMPT_DIRTRIM = 0; + # Because I *am* from EPITA + AGKOZAK_PROMPT_CHAR = [ "42sh$" "42sh#" ":" ]; + # Easy on the eyes + AGKOZAK_COLORS_BRANCH_STATUS = "magenta"; + # I don't like moving my eyes + AGKOZAK_LEFT_PROMPT_ONLY = 1; + }; + + shellAliases = { + # Sometime `gpg-agent` errors out... + reset-agent = "gpg-connect-agent updatestartuptty /bye"; + }; + + # Enable VTE integration when using one of the affected shells + enableVteIntegration = + builtins.any (name: config.my.home.terminal.program == name) [ + "termite" + ]; }; - shellAliases = { - # Sometime `gpg-agent` errors out... - reset-agent = "gpg-connect-agent updatestartuptty /bye"; + # Fuzzy-wuzzy + programs.fzf = { + enable = true; + enableZshIntegration = true; }; - }; - # Fuzzy-wuzzy - config.programs.fzf = lib.mkIf cfg.enable { - enable = true; - enableZshIntegration = true; - }; - - config.programs.dircolors = lib.mkIf cfg.enable { - enable = true; + programs.dircolors = { + enable = true; + }; }; } diff --git a/home/zsh/extra-mappings.zsh b/home/zsh/extra-mappings.zsh index abd6e58..8f7cc4a 100644 --- a/home/zsh/extra-mappings.zsh +++ b/home/zsh/extra-mappings.zsh @@ -1,4 +1,4 @@ -# Fix delete key not working +# Fix delete key not working bindkey "\e[3~" delete-char # Fix Ctrl+u killing from the cursor instead of the whole line diff --git a/home/zsh/options.zsh b/home/zsh/options.zsh index b02ca54..e1e31f4 100644 --- a/home/zsh/options.zsh +++ b/home/zsh/options.zsh @@ -8,5 +8,9 @@ setopt autopushd pushdminus pushdsilent setopt rcquotes # Single word commands can resume an existing job setopt autoresume +# Append commands to history as they are exectuted +setopt inc_append_history_time +# Remove useless whitespace from commands +setopt hist_reduce_blanks # Those options aren't wanted unsetopt beep extendedglob notify diff --git a/lib/attrs.nix b/lib/attrs.nix index 4595467..75114b2 100644 --- a/lib/attrs.nix +++ b/lib/attrs.nix @@ -1,7 +1,50 @@ { lib, ... }: let - inherit (lib) filterAttrs mapAttrs'; + inherit (lib) + filterAttrs + foldl + listToAttrs + mapAttrs' + nameValuePair + recursiveUpdate + ; in { + # Filter a generated set of attrs using a predicate function. + # + # mapFilterAttrs :: + # (name -> value -> bool) + # (name -> value -> { name = any; value = any; }) + # attrs mapFilterAttrs = pred: f: attrs: filterAttrs pred (mapAttrs' f attrs); + + # Generate an attribute set by mapping a function over a list of values. + # + # genAttrs' :: + # [ values ] + # (value -> { name = any; value = any; }) + # attrs + genAttrs' = values: f: listToAttrs (map f values); + + # Merge a list of attrs recursively, later values override previous ones. + # + # recursiveMerge :: + # [ attrs ] + # attrs + recursiveMerge = foldl recursiveUpdate { }; + + # Rename each of the attributes in an attribute set using the mapping function + # + # renameAttrs :: + # (name -> new name) + # attrs + renameAttrs = f: mapAttrs' (name: value: nameValuePair (f name) value); + + # Rename each of the attributes in an attribute set using a function which + # takes the attribute's name and value as inputs. + # + # renameAttrs' :: + # (name -> value -> new name) + # attrs + renameAttrs' = f: mapAttrs' (name: value: nameValuePair (f name value) value); } diff --git a/lib/default.nix b/lib/default.nix index 9014f85..8358d58 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -12,10 +12,7 @@ let }; mylib = makeExtensible (self: - with self; mapModules ./. - (file: import file { inherit self lib pkgs inputs; }) + mapModules ./. (file: import file { inherit self lib pkgs inputs; }) ); in -mylib.extend (self: super: - foldr (a: b: a // b) { } (attrValues super) -) +mylib.extend (self: super: foldr (a: b: a // b) { } (attrValues super)) diff --git a/lib/lists.nix b/lib/lists.nix new file mode 100644 index 0000000..190198e --- /dev/null +++ b/lib/lists.nix @@ -0,0 +1,27 @@ +{ lib, ... }: +let + inherit (lib) filter foldl'; +in +{ + # Count the number of appararitions of each value in a list. + # + # countValues :: + # [ any ] -> ({ any = int; }) + countValues = + let + addToCount = acc: x: + let + v = toString x; + in + acc // { ${v} = (acc.${v} or 0) + 1; }; + in + foldl' addToCount { }; + + # Filter a list using a predicate function after applying a map. + # + # mapFilter :: + # (value -> bool) + # (any -> value) + # [ any ] + mapFilter = pred: f: attrs: filter pred (map f attrs); +} diff --git a/lib/modules.nix b/lib/modules.nix index 92e8476..5b8c4c3 100644 --- a/lib/modules.nix +++ b/lib/modules.nix @@ -3,21 +3,48 @@ let inherit (builtins) readDir pathExists; inherit (lib) hasPrefix hasSuffix nameValuePair removeSuffix; inherit (self.attrs) mapFilterAttrs; + + implOptionalRecursion = recurse: + let + recurseStep = + if recurse + then (n: path: fn: nameValuePair n (impl path fn)) + else (_: _: _: nameValuePair "" null); + impl = dir: fn: + mapFilterAttrs + (n: _: n != "" && !(hasPrefix "_" n)) + (n: v: + let + path = "${toString dir}/${n}"; + in + if v == "directory" + then + if pathExists "${path}/default.nix" + then nameValuePair n (fn path) + else recurseStep n path fn + else if v == "regular" && n != "default.nix" && hasSuffix ".nix" n + then nameValuePair (removeSuffix ".nix" n) (fn path) + else nameValuePair "" null) + (readDir dir); + in + impl; in { - mapModules = dir: fn: - mapFilterAttrs - (n: v: - v != null && - !(hasPrefix "_" n)) - (n: v: - let path = "${toString dir}/${n}"; in - if v == "directory" && pathExists "${path}/default.nix" - then nameValuePair n (fn path) - else if v == "regular" && - n != "default.nix" && - hasSuffix ".nix" n - then nameValuePair (removeSuffix ".nix" n) (fn path) - else nameValuePair "" null) - (readDir dir); + # Find all nix modules in a directory, discard any prefixed with "_", + # map a function to each resulting path, and generate an attribute set + # to associate module name to resulting value. + # + # mapModules :: + # path + # (path -> any) + # attrs + mapModules = implOptionalRecursion false; + + # Recursive version of mapModules. + # + # mapModulesRec :: + # path + # (path -> any) + # attrs + mapModulesRec = implOptionalRecursion true; } diff --git a/lib/strings.nix b/lib/strings.nix new file mode 100644 index 0000000..2a3ec77 --- /dev/null +++ b/lib/strings.nix @@ -0,0 +1,9 @@ +{ ... }: +let +in +{ + # Make an email address from the name and domain stems + # + # mkMailAddress :: String -> String -> String + mkMailAddress = name: domain: "${name}@${domain}"; +} diff --git a/machines/aramis/boot.nix b/machines/aramis/boot.nix new file mode 100644 index 0000000..2169da5 --- /dev/null +++ b/machines/aramis/boot.nix @@ -0,0 +1,32 @@ +{ ... }: +{ + boot = { + loader = { + systemd-boot.enable = true; + efi.canTouchEfiVariables = true; + }; + + initrd = { + availableKernelModules = [ + "nvme" + "sd_mod" + "sdhci_pci" + "usb_storage" + "usbhid" + "xhci_pci" + ]; + kernelModules = [ + "dm-snapshot" + ]; + luks.devices.crypt = { + device = "/dev/nvme0n1p1"; + preLVM = true; + }; + }; + + kernelModules = [ + "kvm-intel" + ]; + extraModulePackages = [ ]; + }; +} diff --git a/porthos.nix b/machines/aramis/default.nix similarity index 52% rename from porthos.nix rename to machines/aramis/default.nix index ce3a200..e2211f4 100644 --- a/porthos.nix +++ b/machines/aramis/default.nix @@ -1,18 +1,27 @@ -# Porthos self-hosted server +# Edit this configuration file to define what should be installed on +# your system. Help is available in the configuration.nix(5) man page +# and in the NixOS manual (accessible by running ‘nixos-help’). + { ... }: { imports = [ - # Include generic settings - ./modules - # Include porthos-specific modules - ./machines/porthos - # Include my secrets - ./secrets - # Include my services - ./services + ./boot.nix + ./hardware.nix + ./home.nix + ./networking.nix + ./profiles.nix + ./programs.nix + ./services.nix + ./sound.nix ]; + # Set your time zone. + time.timeZone = "Europe/Paris"; + + # Enable CUPS to print documents. + services.printing.enable = true; + # This value determines the NixOS release from which the default # settings for stateful data, like file locations and database versions # on your system were taken. It‘s perfectly fine and recommended to leave diff --git a/machines/aramis/hardware.nix b/machines/aramis/hardware.nix new file mode 100644 index 0000000..c66b426 --- /dev/null +++ b/machines/aramis/hardware.nix @@ -0,0 +1,40 @@ +{ lib, modulesPath, ... }: +{ + imports = [ + (modulesPath + "/installer/scan/not-detected.nix") + ]; + + fileSystems = { + "/" = { + device = "/dev/disk/by-label/nixos"; + fsType = "ext4"; + }; + + "/boot" = { + device = "/dev/disk/by-label/boot"; + fsType = "vfat"; + }; + }; + + swapDevices = [ + { device = "/dev/disk/by-label/swap"; } + ]; + + powerManagement.cpuFreqGovernor = lib.mkDefault "powersave"; + + my.hardware = { + firmware = { + cpuFlavor = "intel"; + }; + }; + + hardware = { + trackpoint = { + enable = true; + + emulateWheel = true; # Holding middle buttons allows scrolling + + device = "TPPS/2 Elan TrackPoint"; # Use the correct device name + }; + }; +} diff --git a/machines/aramis/home.nix b/machines/aramis/home.nix new file mode 100644 index 0000000..760174f --- /dev/null +++ b/machines/aramis/home.nix @@ -0,0 +1,32 @@ +{ pkgs, ... }: +{ + my.home = { + # Some amount of social life + discord.enable = true; + # Image viewver + feh.enable = true; + # Firefo profile and extensions + firefox.enable = true; + # Blue light filter + gammastep.enable = true; + # Use a small popup to enter passwords + gpg.pinentry = "gtk2"; + # Machine specific packages + packages.additionalPackages = with pkgs; [ + element-desktop # Matrix client + jellyfin-media-player # Wraps the webui and mpv together + pavucontrol # Audio mixer GUI + quasselClient # IRC client + teams # Work requires it... + transgui # Transmission remote + ]; + # Minimal video player + mpv.enable = true; + # Network-Manager applet + nm-applet.enable = true; + # Termite terminal + terminal.program = "termite"; + # Zathura document viewer + zathura.enable = true; + }; +} diff --git a/machines/aramis/install.sh b/machines/aramis/install.sh new file mode 100755 index 0000000..b03a6df --- /dev/null +++ b/machines/aramis/install.sh @@ -0,0 +1,53 @@ +#!/bin/sh + +set -eu + +if [ "$(id -u)" -ne 0 ]; then + echo "This script must be run as root" >&2 + exit 1 +fi + +SWAP_SIZE=16GiB + +parted /dev/nvme0n1 --script -- \ + mklabel gpt \ + mkpart primary 512MiB 100% \ + mkpart ESP fat32 1MiB 512MiB \ + set 2 esp on + +cryptsetup luksFormat /dev/nvme0n1p1 +cryptsetup open /dev/nvme0n1p1 crypt + +pvcreate /dev/mapper/crypt +vgcreate lvm /dev/mapper/crypt +lvcreate -L "$SWAP_SIZE" -n swap lvm +lvcreate -l 100%FREE -n root lvm + +mkfs.ext4 -L nixos /dev/lvm/root +mkswap -L swap /dev/lvm/swap +mkfs.vfat -n boot /dev/nvme0n1p2 + +mount /dev/disk/by-label/nixos /mnt +mkdir /mnt/boot +mount /dev/nvme0n1p2 /mnt/boot +swapon /dev/lvm/swap + +cat << EOF +# Run the following commands as setup user +nixos-generate-config --root /mnt + +# Change uuids to labels +vim /mnt/etc/nixos/hardware-configuration.nix + +# Install system +mkdir -p /mnt/home/ambroisie/git/nix/config +cd /mnt/home/ambroisie/git/nix/config + +git clone . +# Assuming you set up GPG key correctly +git crypt unlock + +# Setup LUKS with 'boot.initrd.luks.devices.crypt', device is /dev/nvme0n1p1, preLVM = true + +# Use 'nixos-install --flake .#aramis --root /mnt --impure' because of home-manager issue +EOF diff --git a/machines/aramis/networking.nix b/machines/aramis/networking.nix new file mode 100644 index 0000000..fbf4c6b --- /dev/null +++ b/machines/aramis/networking.nix @@ -0,0 +1,19 @@ +{ ... }: +{ + networking = { + hostName = "aramis"; + + # The global useDHCP flag is deprecated, therefore explicitly set to false here. + # Per-interface useDHCP will be mandatory in the future, so this generated config + # replicates the default behaviour. + useDHCP = false; + }; + + my.hardware.networking = { + # Which interface is used to connect to the internet + externalInterface = "enp0s3"; + + # Enable WiFi integration + wireless.enable = true; + }; +} diff --git a/machines/aramis/profiles.nix b/machines/aramis/profiles.nix new file mode 100644 index 0000000..4d2ac7d --- /dev/null +++ b/machines/aramis/profiles.nix @@ -0,0 +1,17 @@ +{ ... }: +{ + my.profiles = { + # Bluetooth configuration and GUI + bluetooth.enable = true; + # Mouse and keyboard configuration + devices.enable = true; + # GTK theme configuration + gtk.enable = true; + # Laptop specific configuration + laptop.enable = true; + # i3 configuration + wm.windowManager = "i3"; + # X configuration + x.enable = true; + }; +} diff --git a/machines/aramis/programs.nix b/machines/aramis/programs.nix new file mode 100644 index 0000000..426ca2a --- /dev/null +++ b/machines/aramis/programs.nix @@ -0,0 +1,7 @@ +{ ... }: +{ + my.programs = { + # Steam configuration + steam.enable = true; + }; +} diff --git a/machines/aramis/services.nix b/machines/aramis/services.nix new file mode 100644 index 0000000..0287c30 --- /dev/null +++ b/machines/aramis/services.nix @@ -0,0 +1,8 @@ +{ ... }: +{ + config.my.services = { + wireguard = { + enable = true; + }; + }; +} diff --git a/machines/aramis/sound.nix b/machines/aramis/sound.nix new file mode 100644 index 0000000..41ff7f7 --- /dev/null +++ b/machines/aramis/sound.nix @@ -0,0 +1,8 @@ +{ ... }: +{ + my.hardware.sound = { + pipewire = { + enable = true; + }; + }; +} diff --git a/machines/porthos/default.nix b/machines/porthos/default.nix index d8726f2..abfc01a 100644 --- a/machines/porthos/default.nix +++ b/machines/porthos/default.nix @@ -9,4 +9,15 @@ ./services.nix ./users.nix ]; + + # Set your time zone. + time.timeZone = "Europe/Paris"; + + # This value determines the NixOS release from which the default + # settings for stateful data, like file locations and database versions + # on your system were taken. It‘s perfectly fine and recommended to leave + # this value at the release version of the first install of this system. + # Before changing this value read the documentation for this option + # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html). + system.stateVersion = "20.09"; # Did you read the comment? } diff --git a/machines/porthos/networking.nix b/machines/porthos/networking.nix index e593eeb..1e2c9cd 100644 --- a/machines/porthos/networking.nix +++ b/machines/porthos/networking.nix @@ -31,5 +31,5 @@ }; # Which interface is used to connect to the internet - my.networking.externalInterface = "eth0"; + my.hardware.networking.externalInterface = "eth0"; } diff --git a/machines/porthos/services.nix b/machines/porthos/services.nix index 9bc99e7..2661121 100644 --- a/machines/porthos/services.nix +++ b/machines/porthos/services.nix @@ -1,7 +1,7 @@ # Deployed services -{ config, ... }: +{ config, lib, ... }: let - my = config.my; + secrets = config.age.secrets; in { # List services that you want to enable: @@ -19,11 +19,8 @@ in OnActiveSec = "6h"; OnUnitActiveSec = "6h"; }; - # Insecure, I don't care. - passwordFile = - builtins.toFile "password.txt" my.secrets.backup.password; - credentialsFile = - builtins.toFile "creds.env" my.secrets.backup.credentials; + passwordFile = secrets."backup/password".path; + credentialsFile = secrets."backup/credentials".path; }; # My blog and related hosts blog.enable = true; @@ -34,54 +31,100 @@ in drone = { enable = true; runners = [ "docker" "exec" ]; - # Insecure, I don't care. - secretFile = - builtins.toFile "gitea.env" my.secrets.drone.gitea; - sharedSecretFile = - builtins.toFile "rpc.env" my.secrets.drone.secret; + secretFile = secrets."drone/gitea".path; + sharedSecretFile = secrets."drone/secret".path; + }; + # Flood UI for transmission + flood = { + enable = true; }; # Gitea forge - gitea.enable = true; + gitea = { + enable = true; + mail = { + enable = true; + host = "smtp.migadu.com:465"; + user = lib.my.mkMailAddress "gitea" "belanyi.fr"; + passwordFile = secrets."gitea/mail-password".path; + }; + }; + # Grocy ERP + grocy = { + enable = true; + }; # Meta-indexers indexers = { - jackett.enable = true; - nzbhydra.enable = true; + prowlarr.enable = true; }; # Jellyfin media server jellyfin.enable = true; # Gitea mirrorig service lohr = { enable = true; - sharedSecretFile = - let - content = "LOHR_SECRET=${my.secrets.lohr.secret}"; - in - builtins.toFile "lohr-secret.env" content; + sharedSecretFile = secrets."lohr/secret".path; + sshKeyFile = secrets."lohr/ssh-key".path; }; # Matrix backend and Element chat front-end matrix = { enable = true; - secret = my.secrets.matrix.secret; + mailConfigFile = secrets."matrix/mail".path; + bridges = { + enable = true; + }; + # Only necessary when doing the initial registration + # secret = "change-me"; }; miniflux = { enable = true; - password = my.secrets.miniflux.password; + credentialsFiles = secrets."miniflux/credentials".path; + }; + # Various monitoring dashboards + monitoring = { + enable = true; + grafana = { + passwordFile = secrets."monitoring/password".path; + }; + }; + # FLOSS music streaming server + navidrome = { + enable = true; + musicFolder = "/data/media/music"; }; # Nextcloud self-hosted cloud nextcloud = { enable = true; - password = my.secrets.nextcloud.password; + passwordFile = secrets."nextcloud/password".path; + }; + nginx = { + enable = true; + acme = { + credentialsFile = secrets."acme/dns-key".path; + }; + sso = { + authKeyFile = secrets."sso/auth-key".path; + users = { + ambroisie = { + passwordHashFile = secrets."sso/ambroisie/password-hash".path; + totpSecretFile = secrets."sso/ambroisie/totp-secret".path; + }; + }; + groups = { + root = [ "ambroisie" ]; + }; + }; + }; + paperless = { + enable = true; + documentPath = "/data/media/paperless"; + passwordFile = secrets."paperless/password".path; + secretKeyFile = secrets."paperless/secret-key".path; }; # The whole *arr software suite pirate.enable = true; # Podcast automatic downloader podgrab = { enable = true; - passwordFile = - let - contents = "PASSWORD=${my.secrets.podgrab.password}"; - in - builtins.toFile "podgrab.env" contents; + passwordFile = secrets."podgrab/password".path; port = 9598; }; # Regular backups @@ -97,8 +140,7 @@ in # Torrent client and webui transmission = { enable = true; - username = "Ambroisie"; - password = my.secrets.transmission.password; + credentialsFile = secrets."transmission/credentials".path; }; # Simple, in-kernel VPN wireguard = { @@ -106,9 +148,4 @@ in startAtBoot = true; # Server must be started to ensure clients can connect }; }; - - programs.gnupg.agent = { - enable = true; - enableSSHSupport = true; - }; } diff --git a/machines/porthos/ssh/drone.pub b/machines/porthos/ssh/drone.pub new file mode 100644 index 0000000..d1f971c --- /dev/null +++ b/machines/porthos/ssh/drone.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDWVUvOT1/triPcj7wiLAmiVkPZ71crySbReetHaGxMYYdKNFurJQsP6BqsdCAwrGbduLUDJovLtjOM7SxghjkGqh2RZucj/zqpja8YoFqYTcLutlqa1NwUqQTq21azKBDSdvkBPWWyZhOKssnag+0bZRN3vVajoDrwAU6zJLhHh9eNESTEytAnZnllXsHB1dKF1p7FWVwYTGAc1PdHHSQNMkjg9aCM+VBzTHhp8nF+GOtGzt0A0XnoZGdhn6KqhKyH7KxwPMmeD3RNeCEmQY/TXjthOx/mBkgTEa8LWOBxdy/Rs6edUenvPcQ5tK5nX0GSxxqtbORlhT+tGiqq1UHeIUhXirBUaS7pnDo+Edc0m8ruLcwHwyQ5yVn2ts3daKxb87+PyjYiRxxeQXvbF84ef7ZOkLTEn0tnftFHLqszBfOjoV1DmMNSWPDULD3krObzNbr1I0xHE7bDRXw2t8L1cwHOLHTL9KwsTCw1d25JSxINp2wAZxlGVZLXoXVMKTjfx1xSGbUuRzA1Q6+1IH9WDvSSixDzvc2Dqnj91/xardivApK+T+OxTBurwWsxzEezIAbTCpoKW9ulzu06xWGWhxATkzUmVh/qhFUHAVlmhEvn0KqlYbWteEcUxgKS1uSAoA6+pZh5NMG1u1hLEktBQbDnS0VdyKYBUHZidLuR4w== ambroisie@porthos diff --git a/machines/porthos/users.nix b/machines/porthos/users.nix index 1a26e3c..fbbe368 100644 --- a/machines/porthos/users.nix +++ b/machines/porthos/users.nix @@ -1,8 +1,5 @@ # User setup -{ config, ... }: -let - my = config.my; -in +{ ... }: { users.users.blog = { description = "Blog Publisher"; @@ -10,6 +7,6 @@ in group = "nginx"; createHome = false; # Messes with permissions home = "/var/www/"; - openssh.authorizedKeys.keys = [ my.secrets.drone.ssh.publicKey ]; + openssh.authorizedKeys.keyFiles = [ ./ssh/drone.pub ]; }; } diff --git a/modules/default.nix b/modules/default.nix index 082a8da..2eaa2e6 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -1,15 +1,28 @@ # Common modules -{ ... }: +{ lib, ... }: { imports = [ - ./documentation.nix - ./ergodox.nix - ./language.nix - ./media.nix - ./networking.nix - ./nix.nix - ./packages.nix - ./users.nix + ./hardware + ./home + ./programs + ./secrets + ./services + ./system ]; + + options.my = with lib; { + user = { + name = mkOption { + type = types.str; + default = "ambroisie"; + example = "alice"; + description = "my username"; + }; + + home = { + enable = my.mkDisableOption "home-manager configuration"; + }; + }; + }; } diff --git a/modules/documentation.nix b/modules/documentation.nix deleted file mode 100644 index 5f67197..0000000 --- a/modules/documentation.nix +++ /dev/null @@ -1,37 +0,0 @@ -{ config, lib, ... }: -let - cfg = config.my.module.documentation; - - # I usually want everything enabled at once, but keep it customizable - defaultToGlobal = description: lib.mkEnableOption description // { - default = cfg.enable; - }; -in -{ - options.my.module.documentation = with lib.my; { - enable = mkDisableOption "Documentation integration"; - - dev.enable = defaultToGlobal "Documentation aimed at developers"; - - info.enable = defaultToGlobal "Documentation aimed at developers"; - - man.enable = defaultToGlobal "Documentation aimed at developers"; - - nixos.enable = defaultToGlobal "NixOS documentation"; - }; - - config.documentation = { - enable = cfg.enable; - - dev.enable = cfg.dev.enable; - - info.enable = cfg.info.enable; - - man = { - enable = cfg.man.enable; - generateCaches = true; - }; - - nixos.enable = cfg.nixos.enable; - }; -} diff --git a/modules/hardware/bluetooth/default.nix b/modules/hardware/bluetooth/default.nix new file mode 100644 index 0000000..ffe0fbe --- /dev/null +++ b/modules/hardware/bluetooth/default.nix @@ -0,0 +1,75 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.hardware.bluetooth; +in +{ + options.my.hardware.bluetooth = with lib; { + enable = mkEnableOption "bluetooth configuration"; + + enableHeadsetIntegration = my.mkDisableOption "A2DP sink configuration"; + + loadExtraCodecs = my.mkDisableOption "extra audio codecs"; + }; + + config = lib.mkIf cfg.enable (lib.mkMerge [ + # Enable bluetooth devices and GUI to connect to them + { + hardware.bluetooth.enable = true; + services.blueman.enable = true; + } + + # Support for additional bluetooth codecs + (lib.mkIf cfg.loadExtraCodecs { + hardware.pulseaudio = { + extraModules = [ pkgs.pulseaudio-modules-bt ]; + package = pkgs.pulseaudioFull; + }; + + services.pipewire = { + media-session.config.bluez-monitor.rules = [ + { + # Matches all cards + matches = [{ "device.name" = "~bluez_card.*"; }]; + actions = { + "update-props" = { + "bluez5.reconnect-profiles" = [ + "hfp_hf" + "hsp_hs" + "a2dp_sink" + ]; + # mSBC provides better audio + microphone + "bluez5.msbc-support" = true; + # SBC XQ provides better audio + "bluez5.sbc-xq-support" = true; + }; + }; + } + { + matches = [ + # Matches all sources + { + "node.name" = "~bluez_input.*"; + } + # Matches all outputs + { + "node.name" = "~bluez_output.*"; + } + ]; + actions = { + "node.pause-on-idle" = false; + }; + } + ]; + }; + }) + + # Support for A2DP audio profile + (lib.mkIf cfg.enableHeadsetIntegration { + hardware.bluetooth.settings = { + General = { + Enable = "Source,Sink,Media,Socket"; + }; + }; + }) + ]); +} diff --git a/modules/hardware/default.nix b/modules/hardware/default.nix new file mode 100644 index 0000000..2a686f7 --- /dev/null +++ b/modules/hardware/default.nix @@ -0,0 +1,14 @@ +# Hardware-related modules +{ ... }: + +{ + imports = [ + ./bluetooth + ./ergodox + ./firmware + ./mx-ergo + ./networking + ./sound + ./upower + ]; +} diff --git a/modules/ergodox.nix b/modules/hardware/ergodox/default.nix similarity index 64% rename from modules/ergodox.nix rename to modules/hardware/ergodox/default.nix index cbc4957..77f3ecb 100644 --- a/modules/ergodox.nix +++ b/modules/hardware/ergodox/default.nix @@ -1,16 +1,14 @@ # ZSA keyboard udev rules { config, lib, ... }: let - cfg = config.my.modules.ergodox; + cfg = config.my.hardware.ergodox; in { - options.my.modules.ergodox = with lib; { + options.my.hardware.ergodox = with lib; { enable = mkEnableOption "ZSA udev rules and user group configuration"; }; config = lib.mkIf cfg.enable { hardware.keyboard.zsa.enable = true; - - users.extraGroups = [ "plugdev" ]; }; } diff --git a/modules/hardware/firmware/default.nix b/modules/hardware/firmware/default.nix new file mode 100644 index 0000000..e899232 --- /dev/null +++ b/modules/hardware/firmware/default.nix @@ -0,0 +1,38 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.hardware.firmware; +in +{ + options.my.hardware.firmware = with lib; { + enable = my.mkDisableOption "firmware configuration"; + + cpuFlavor = mkOption { + type = with types; nullOr (enum [ "intel" "amd" ]); + default = null; + example = "intel"; + description = "Which kind of CPU to activate micro-code updates"; + }; + }; + + config = lib.mkIf cfg.enable (lib.mkMerge [ + { + hardware = { + enableRedistributableFirmware = true; + }; + } + + # Intel CPU + (lib.mkIf (cfg.cpuFlavor == "intel") { + hardware = { + cpu.intel.updateMicrocode = true; + }; + }) + + # AMD CPU + (lib.mkIf (cfg.cpuFlavor == "amd") { + hardware = { + cpu.amd.updateMicrocode = true; + }; + }) + ]); +} diff --git a/modules/hardware/mx-ergo/default.nix b/modules/hardware/mx-ergo/default.nix new file mode 100644 index 0000000..e4e55a1 --- /dev/null +++ b/modules/hardware/mx-ergo/default.nix @@ -0,0 +1,26 @@ +# Hold down the `next page` button to scroll using the ball +{ config, lib, ... }: +let + cfg = config.my.hardware.mx-ergo; +in +{ + options.my.hardware.mx-ergo = with lib; { + enable = mkEnableOption "MX Ergo configuration"; + }; + + config = lib.mkIf cfg.enable { + services.xserver = { + # This section must be *after* the one configured by `libinput` + # for the `ScrollMethod` configuration to not be overriden + inputClassSections = lib.mkAfter [ + '' + Identifier "MX Ergo scroll button configuration" + MatchProduct "MX Ergo" + MatchIsPointer "on" + Option "ScrollMethod" "button" + Option "ScrollButton" "9" + '' + ]; + }; + }; +} diff --git a/modules/hardware/networking/default.nix b/modules/hardware/networking/default.nix new file mode 100644 index 0000000..f0806fe --- /dev/null +++ b/modules/hardware/networking/default.nix @@ -0,0 +1,27 @@ +{ config, lib, ... }: +let + cfg = config.my.hardware.networking; +in +{ + options.my.hardware.networking = with lib; { + externalInterface = mkOption { + type = types.nullOr types.str; + default = null; + example = "eth0"; + description = '' + Name of the network interface that egresses to the internet. Used for + e.g. NATing internal networks. + ''; + }; + + wireless = { + enable = mkEnableOption "wireless configuration"; + }; + }; + + config = lib.mkMerge [ + (lib.mkIf cfg.wireless.enable { + networking.networkmanager.enable = true; + }) + ]; +} diff --git a/modules/hardware/sound/default.nix b/modules/hardware/sound/default.nix new file mode 100644 index 0000000..e8ba7f7 --- /dev/null +++ b/modules/hardware/sound/default.nix @@ -0,0 +1,63 @@ +{ config, lib, ... }: +let + cfg = config.my.hardware.sound; +in +{ + options.my.hardware.sound = with lib; { + pipewire = { + enable = mkEnableOption "pipewire configuration"; + }; + + pulse = { + enable = mkEnableOption "pulseaudio configuration"; + }; + }; + + config = (lib.mkMerge [ + # Sanity check + { + assertions = [ + { + assertion = builtins.all (lib.id) [ + (cfg.pipewire.enable -> !cfg.pulse.enable) + (cfg.pulse.enable -> !cfg.pipewire.enable) + ]; + message = '' + `config.my.hardware.sound.pipewire.enable` and + `config.my.hardware.sound.pulse.enable` are incompatible. + ''; + } + ]; + } + + (lib.mkIf cfg.pipewire.enable { + # RealtimeKit is recommended + security.rtkit.enable = true; + + services.pipewire = { + enable = true; + + alsa = { + enable = true; + support32Bit = true; + }; + + pulse = { + enable = true; + }; + + jack = { + enable = true; + }; + }; + }) + + # Pulseaudio setup + (lib.mkIf cfg.pulse.enable { + # ALSA + sound.enable = true; + + hardware.pulseaudio.enable = true; + }) + ]); +} diff --git a/modules/hardware/upower/default.nix b/modules/hardware/upower/default.nix new file mode 100644 index 0000000..95fa282 --- /dev/null +++ b/modules/hardware/upower/default.nix @@ -0,0 +1,44 @@ +{ config, lib, ... }: +let + cfg = config.my.hardware.upower; +in +{ + options.my.hardware.upower = with lib; { + enable = mkEnableOption "upower configuration"; + + levels = { + low = mkOption { + type = types.ints.unsigned; + default = 25; + example = 10; + description = "Low percentage"; + }; + + critical = mkOption { + type = types.ints.unsigned; + default = 15; + example = 5; + description = "Critical percentage"; + }; + + action = mkOption { + type = types.ints.unsigned; + default = 5; + example = 3; + description = "Percentage at which point an action must be taken"; + }; + }; + }; + + config = lib.mkIf cfg.enable { + services.upower = { + enable = true; + + percentageLow = cfg.levels.low; + + percentageCritical = cfg.levels.critical; + + percentageAction = cfg.levels.action; + }; + }; +} diff --git a/modules/home/default.nix b/modules/home/default.nix new file mode 100644 index 0000000..a287f35 --- /dev/null +++ b/modules/home/default.nix @@ -0,0 +1,29 @@ +{ config, inputs, lib, ... }: +let + actualPath = [ "home-manager" "users" config.my.user.name "my" "home" ]; + aliasPath = [ "my" "home" ]; + + cfg = config.my.user.home; +in +{ + imports = [ + inputs.home-manager.nixosModule # enable home-manager options + (lib.mkAliasOptionModule aliasPath actualPath) # simplify setting home options + ]; + + config = lib.mkIf cfg.enable { + home-manager = { + # Not a fan of out-of-directory imports, but this is a good exception + users.${config.my.user.name} = import ../../home; + + # Nix Flakes compatibility + useGlobalPkgs = true; + useUserPackages = true; + + # Forward inputs to home-manager configuration + extraSpecialArgs = { + inherit inputs; + }; + }; + }; +} diff --git a/modules/language.nix b/modules/language.nix deleted file mode 100644 index 48d9f30..0000000 --- a/modules/language.nix +++ /dev/null @@ -1,7 +0,0 @@ -# Language settings -{ ... }: - -{ - # Select internationalisation properties. - i18n.defaultLocale = "en_US.UTF-8"; -} diff --git a/modules/media.nix b/modules/media.nix deleted file mode 100644 index 4ad2fee..0000000 --- a/modules/media.nix +++ /dev/null @@ -1,16 +0,0 @@ -# Abstracting away the need for a common 'media' group - -{ config, lib, ... }: -let - mediaServices = with config.my.services; [ - calibre-web - jellyfin - pirate - sabnzbd - transmission - ]; - needed = builtins.any (service: service.enable) mediaServices; -in -{ - config.users.groups.media = lib.mkIf needed { }; -} diff --git a/modules/networking.nix b/modules/networking.nix deleted file mode 100644 index 28ba108..0000000 --- a/modules/networking.nix +++ /dev/null @@ -1,13 +0,0 @@ -{ lib, ... }: - -{ - options.my.networking.externalInterface = with lib; mkOption { - type = types.nullOr types.str; - default = null; - example = "eth0"; - description = '' - Name of the network interface that egresses to the internet. Used for - e.g. NATing internal networks. - ''; - }; -} diff --git a/modules/nix.nix b/modules/nix.nix deleted file mode 100644 index fb601a5..0000000 --- a/modules/nix.nix +++ /dev/null @@ -1,11 +0,0 @@ -# Nix related settings -{ pkgs, ... }: - -{ - nix = { - package = pkgs.nixFlakes; - extraOptions = '' - experimental-features = nix-command flakes - ''; - }; -} diff --git a/modules/packages.nix b/modules/packages.nix deleted file mode 100644 index 9eb50f6..0000000 --- a/modules/packages.nix +++ /dev/null @@ -1,19 +0,0 @@ -# Common packages -{ pkgs, ... }: - -{ - # List packages installed in system profile. To search, run: - # $ nix search wget - environment.systemPackages = with pkgs; [ - git - git-crypt - mosh - vim - wget - ]; - - programs.vim.defaultEditor = true; # Modal editing is life - programs.zsh.enable = true; # Use integrations - - nixpkgs.config.allowUnfree = true; # Because I don't care *that* much. -} diff --git a/modules/programs/default.nix b/modules/programs/default.nix new file mode 100644 index 0000000..73f4e4b --- /dev/null +++ b/modules/programs/default.nix @@ -0,0 +1,8 @@ +# Program-related modules +{ ... }: + +{ + imports = [ + ./steam + ]; +} diff --git a/modules/programs/steam/default.nix b/modules/programs/steam/default.nix new file mode 100644 index 0000000..dbdc0ce --- /dev/null +++ b/modules/programs/steam/default.nix @@ -0,0 +1,39 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.programs.steam; +in +{ + options.my.programs.steam = with lib; { + enable = mkEnableOption "steam configuration"; + + dataDir = mkOption { + type = types.str; + default = "$XDG_DATA_HOME/steamlib"; + example = "/mnt/steam/"; + description = '' + Which directory should be used as HOME to run steam. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + programs.steam = { + enable = true; + }; + + environment.systemPackages = builtins.map lib.hiPrio [ + # Respect XDG conventions, leave my HOME alone + (pkgs.writeScriptBin "steam" '' + #!/bin/sh + mkdir -p "${cfg.dataDir}" + HOME="${cfg.dataDir}" exec ${pkgs.steam}/bin/steam "$@" + '') + # Same, for GOG and other such games + (pkgs.writeScriptBin "steam-run" '' + #!/bin/sh + mkdir -p "${cfg.dataDir}" + HOME="${cfg.dataDir}" exec ${pkgs.steam-run-native}/bin/steam-run "$@" + '') + ]; + }; +} diff --git a/modules/secrets/acme/dns-key.age b/modules/secrets/acme/dns-key.age new file mode 100644 index 0000000..97d397c --- /dev/null +++ b/modules/secrets/acme/dns-key.age @@ -0,0 +1,10 @@ +age-encryption.org/v1 +-> ssh-ed25519 cKojmg 0bz3W8QcGaulxy+kDmM717jTthQpFOCwV9HkenFJEyo +NKeh1/JkX4WAWbOjUeKLMbsyCevnDf3a70FfYUav26c +-> ssh-ed25519 jPowng Q59ybJMMteOSB6hZ5m6UPP0N2p8jrDSu5vBYwPgGcRw +j420on2jSsfMsv4MDtiOTMIFjaXV7sIsrS+g4iab+68 +-> z}.q-grease s2W ssh-ed25519 cKojmg YlDuj9wwBKSHHvQOhfti1ah95vxDV3bLE+GElBkyTB0 +KsMyd3L4GaQa0eDQps+bJXj+cpy0zUNvFXU8NAmtThI +-> ssh-ed25519 jPowng JB4UtNyZab4ab4Pep3acyMjwCbluuEPuI6YOQ/045Fo +P9qnrPDGpHJL1TyNqYdNfqkd21Yjn/5mlovorWy60j4 +-> _6l|s-grease M ]2qMsa'w P] j0EE +W3CToUTg +--- 8aWYUi33mEIKFcFbphlDZumnBu9Xbj+j18dQbElx1v8 +3$m(øäÂTK±î·”eAZâ>dn:-­Òí‚¥ˆÅh.›(¶U²!rìx D3ô‡4Ø93~È»f{üƒšL¸Î þÆ£ÃØ>Þß^v›l—¡Î-=„í¯ä£ÉU'â»(,µ#;¤ªHñÆ@M%|ʦ \ No newline at end of file diff --git a/modules/secrets/backup/password.age b/modules/secrets/backup/password.age new file mode 100644 index 0000000..3af9fbe --- /dev/null +++ b/modules/secrets/backup/password.age @@ -0,0 +1,8 @@ +age-encryption.org/v1 +-> ssh-ed25519 cKojmg dgS4bezgtDi44R1A8am+J6zh80kUVYTo1heaxJCtzX4 +F3w/62xwtqYa40NU7OvF9pnZzYz/5hACAGJfMA4e2zw +-> ssh-ed25519 jPowng lx81CK3yeNp9RjHCUFJeKYZlRzxBmXuADVBvRc13zCI +P7e75t8xU+ZkYmeQ8mmMfyZZsRdG1J8yrvSUkiWzkFQ +-> *z4/`-grease S/)a{e sFd";= +--- 15FVhqRTkoPFEeETRRyFQhsv4Fn19Ozlax0u8Zy9mNA +õ#+¥àÎvøSÈ4èá}§Rì%‹Î¯F4fnDœ˜J¹¤Z‹¸A¥Û™,_ \ No newline at end of file diff --git a/modules/secrets/default.nix b/modules/secrets/default.nix new file mode 100644 index 0000000..e8cb866 --- /dev/null +++ b/modules/secrets/default.nix @@ -0,0 +1,29 @@ +{ config, inputs, lib, options, ... }: + +{ + imports = [ + inputs.agenix.nixosModules.age + ]; + + config.age = { + secrets = + let + toName = lib.removeSuffix ".age"; + userExists = u: builtins.hasAttr u config.users.users; + # Only set the user if it exists, to avoid warnings + userIfExists = u: if userExists u then u else "root"; + toSecret = name: { owner ? "root", ... }: { + file = ./. + "/${name}"; + owner = lib.mkDefault (userIfExists owner); + }; + convertSecrets = n: v: lib.nameValuePair (toName n) (toSecret n v); + secrets = import ./secrets.nix; + in + lib.mapAttrs' convertSecrets secrets; + + identityPaths = options.age.identityPaths.default ++ [ + # FIXME: hard-coded path, could be inexistent + "/home/ambroisie/.ssh/id_ed25519" + ]; + }; +} diff --git a/modules/secrets/drone/gitea.age b/modules/secrets/drone/gitea.age new file mode 100644 index 0000000..d1c14e7 --- /dev/null +++ b/modules/secrets/drone/gitea.age @@ -0,0 +1,10 @@ +age-encryption.org/v1 +-> ssh-ed25519 cKojmg vLLu1kbzyGxr5sU/Dl4xf0uGO+gVsvODiqEJU21lwyI +LbJO4Go+8G7/UtFWjv+x7Nqhn7n+kge/oHP8dGCBnM8 +-> ssh-ed25519 jPowng obxX4ojPwp/DaerFzVbK5hUnshebh/chriT3a7uqYEw +x9jpbBefJZHz8o1lEkr48XhT7sVAM5tq3tZ8M91CDDo +-> eZ.G`B3W-grease 6k|.\v +D0u3P4oCpPNnueqZAAYn71xEUGWlavwLTrEXJ+2tdYOX6BwwFReOlMZWIA+FikmZ +8Pg7dHnbYPWc33jMjv3UnNsxCGUsDw9C9NkI5vfZSLvUxQ +--- Cea09ivsGZeoWif7xbdrvfoGsoiD+tRh7HQsOL75cqE +t‘Fa˜|GÐ, ìoå6Öù$ë×ý…U«"âwiß¹ªÈS½Ó¿î›wà×ghµ6^Ð*=¬¦©[¬g1%çVuäápû©-›™ï{›`ÑPÅ(?&¼QV#îKeåX•4dß÷KÞ:šxt‰0LsbÆ6Þœßü Ð[¡ #E[¬í•¹ì>Ë)|cwÅëÑqŸÊö+cÄõ¶þÕw1$ÓÌý×Ò^I(wGÕ¡ç9>jIâ(yÌ!@O«ƉkE¼z]áí«Pk \ No newline at end of file diff --git a/modules/secrets/drone/secret.age b/modules/secrets/drone/secret.age new file mode 100644 index 0000000..c529200 --- /dev/null +++ b/modules/secrets/drone/secret.age @@ -0,0 +1,9 @@ +age-encryption.org/v1 +-> ssh-ed25519 cKojmg 1+cLlzctgcM0FnVDwMPOAqBkvMcDBRg8SvCw4djI93Y +oV2XI4f1AvM9P591kZZ6NgJXa+SDtqGzCSgc4psOmxM +-> ssh-ed25519 jPowng Ufjfh1p350XxRPg95+/DHdmnl4lC0bbzUUlaxd1Bmxc +/RHwFDSn2ov+60r1uHUigrsn99+GmmKmlk4h4T2gbA0 +-> *Lc$@-grease +pzVJAHy1qRq3jUrnFV0DDO7/hwV1US4Ogf0RsrVfX0xzbr73uJ003YjieVB25LqN +--- ME7/iVevyiguyhXugbkVFGzJV0yDccyKNlWbEZa/FmY +YžŠXjb2uþnd;i0íýX]…§é0–þjé’L„PÔT~óú ƒÙ^kc”$D×ÚÛr¹úu³¶fr€e¸OÕ¸þ+p•¨&ãw®öϨ \ No newline at end of file diff --git a/modules/secrets/drone/ssh/private-key.age b/modules/secrets/drone/ssh/private-key.age new file mode 100644 index 0000000..0211701 Binary files /dev/null and b/modules/secrets/drone/ssh/private-key.age differ diff --git a/modules/secrets/gitea/mail-password.age b/modules/secrets/gitea/mail-password.age new file mode 100644 index 0000000..915f8e9 --- /dev/null +++ b/modules/secrets/gitea/mail-password.age @@ -0,0 +1,9 @@ +age-encryption.org/v1 +-> ssh-ed25519 jPowng BkIjie2KrwDLaZYYIguCs7TPA/wQy+YPguikuhfye0M +7viTA/EGYB/jRKQm6fFd86DMd4j+Jxsaw/xQ1T8ZKNo +-> ssh-ed25519 cKojmg t1Y8bZvPccNAX8vWQLTfCyOJIBXN515vyfFrEI2EVww +bJEjpIWrKeQrA/JfY7FRdB6hpHwR/aG4Vya1ChFNBKs +-> jK/-grease Oz.R ?;)G ], +AuHk9TcC9kl0dg8/L6UfHIk3e9fgGwSTJAJpVgInhok +--- 47z9lol5MtpX0IsO/0ggLDMcNVfl4lNNvoHUSwOU/18 +)gЪeuÞ! œš- ÞTì¥YAðM+ˆãGbMe@­|A,è&ãÆE!܆p=P²=û9¹ÙP¹!Üö’Q|Ðä r \ No newline at end of file diff --git a/modules/secrets/lohr/secret.age b/modules/secrets/lohr/secret.age new file mode 100644 index 0000000..fa310b4 --- /dev/null +++ b/modules/secrets/lohr/secret.age @@ -0,0 +1,9 @@ +age-encryption.org/v1 +-> ssh-ed25519 cKojmg HCVbkI26JjkBgm1L2cpunVui0PfHLNfnx6VczErF3A4 +3jEHfT6wUqNNFZFaVeiNBUhSKZmuKclPmubDMsda5O8 +-> ssh-ed25519 jPowng SyClv9kGtjRKSXdig27tiqp66wD1T8QsHeOD2JQl4QA +8zdtfSJEh5/bfu5tb6M8Jgy5CZPiWD8TLQDpzp6cTr0 +-> 3r2-grease +Lg/G911eZjeZTw5xhqje26vDfJkcSro+gKQ5SUboxLMnaibNi1qTeRLR +--- Q5/fikhVPoK+NFujTso5V7cty4k/dQlzFlz5z9DkzYk + øt/ŒW‹AMuˆ"Þêð´—ó-!@ ›¨E1¯” äR[eŽ’hÖû3 ëŒÉÐScoÝBt1TýØb¨äÀ3möP×Tc¤feP \ No newline at end of file diff --git a/modules/secrets/lohr/ssh-key.age b/modules/secrets/lohr/ssh-key.age new file mode 100644 index 0000000..30a5e25 Binary files /dev/null and b/modules/secrets/lohr/ssh-key.age differ diff --git a/modules/secrets/matrix/mail.age b/modules/secrets/matrix/mail.age new file mode 100644 index 0000000..1fe3a71 --- /dev/null +++ b/modules/secrets/matrix/mail.age @@ -0,0 +1,9 @@ +age-encryption.org/v1 +-> ssh-ed25519 cKojmg lmu3MinmydRHD0A/YVRRtopermfoBC8M8cTHfVanY1s +ygrtpZZJ7aeQTblNazpoP7DdifmDxHsE3DFJsIrWX5M +-> ssh-ed25519 jPowng X0cihOc+fBtmtrkEivIHQngdYIobezXEF1x+pHqNzAw +/+sw9x1NWY0anZhDMpAywBPrR0F4XCHaF9e8j/Yo/kI +-> 32;%1s-grease +JafjuSZty6a4NSO/y4y5wHWL8Mw +--- dwCl66vdpsL0MR5NWWvg3JUnQ2QZQBeW0Dj0l5tvOKY +oi,`ÓÜ#uÄwW%PoubÚ­cy8ó ƒÃÉ><¿F‰Ååq…ÂKÂÇk0Çk/hÀ¥Ÿ5势ÝF+ýu‡ •e€¾Ÿ²óôbãè>1QŠ2®ñwn˜WbÖ–B˜âîiŸ^xurâ†- /llùÒÀÀ-ã=°7;jã0»I×%Fi¼í€ø‹™A;Y†ìUd]KÅI0(½ ”øAg£Ðóž^†uG:äpkJ’Ÿ:q¢šWSaLw¯¿Ô!ïM³4ã L/ùZŇ®¢D¶-XéUb»‘vÊbP‚ó›0ÇÅfÂ9êú †âJ`ÃX°ôÐOÅ!s›{ÙÄQAšc€c;ÏÃÑ‹4öMíچݹ lxH&ïéöé{é}ÁäÛzZ¦œ‚9ûÊXžÜ“g‰]Vϱ•0gt¡¿…žw· \ No newline at end of file diff --git a/modules/secrets/matrix/secret.age b/modules/secrets/matrix/secret.age new file mode 100644 index 0000000..a287435 --- /dev/null +++ b/modules/secrets/matrix/secret.age @@ -0,0 +1,9 @@ +age-encryption.org/v1 +-> ssh-ed25519 cKojmg ociW6AZww4nfW0Dw0DB0WNgQbJ3MNkHPPZlA0z+o/mI +THAz89pjyrkxJB9tPQGgEwZrZX9OudWMnyzr0JiwzTA +-> ssh-ed25519 jPowng 1werbtuWK0DUFxq9mAWp/QzMHC1B8UfadutvK6+j9XE +YmAwYo3X00gMB9AyQfOsR82CUPAtxfuzCzP4OyYFxjc +-> 8g-grease N9DR4 .U< +--- Cwh2hPrM2RzRroJRw3XrP1khcpL0leTXfJ+T7WG57To +¡Â±jϰLæDFºðÔ xuxý1 +U/âàoÚGgoãË)Çê÷*Þï/Ç”dÈ"L#RõÄhWPÛ Íû« \ No newline at end of file diff --git a/modules/secrets/miniflux/credentials.age b/modules/secrets/miniflux/credentials.age new file mode 100644 index 0000000..9790159 Binary files /dev/null and b/modules/secrets/miniflux/credentials.age differ diff --git a/modules/secrets/monitoring/password.age b/modules/secrets/monitoring/password.age new file mode 100644 index 0000000..410536f --- /dev/null +++ b/modules/secrets/monitoring/password.age @@ -0,0 +1,10 @@ +age-encryption.org/v1 +-> ssh-ed25519 cKojmg OdLtFHbHbc28rUn47vgsVvXxFNg9nF+9y9R6XOK390Y +yQQYUPQGjN2+xrSqqBYa7/zS618KrVjX5Amw2MFuSLg +-> ssh-ed25519 jPowng NwUjiLtiXVi6XFmht5l1CxEs3gm0oN4vHYwDZyda7Q4 +di6znVjNRO6QdqteVNkeot5Ko2NwWLe6v+zVR3f+o10 +-> 4Vx%\(-grease ^^Z>EC91 R 2BJ d48Wip*s +yPiBgChRF31XgxccQFLO3MzRL7+5s29sfRoF3W1yUX6Bu59MpxD4D+n/jhLcxSH/ +CxW7KaiOctNmPm5tWh6qjmgQ+V4bcAji5vo4FKs40l56cfyueEJj+Q +--- WUGF28zqK9E1AlOeeCtSHxFg6ikRy85gOoLtBd4m0y0 +.|…rr>©†ðìì1ÅÆ2SÉž.×hww qºš%i˜øé ‚*U^­)Öè'qžµ›O2ÓœümòQÝ7˜¯m` \ No newline at end of file diff --git a/modules/secrets/nextcloud/password.age b/modules/secrets/nextcloud/password.age new file mode 100644 index 0000000..9fd3c53 Binary files /dev/null and b/modules/secrets/nextcloud/password.age differ diff --git a/modules/secrets/paperless/password.age b/modules/secrets/paperless/password.age new file mode 100644 index 0000000..3fe76cb --- /dev/null +++ b/modules/secrets/paperless/password.age @@ -0,0 +1,10 @@ +age-encryption.org/v1 +-> ssh-ed25519 cKojmg zhpo89xef68JoeOFWzhdFshrj2BXXUCFPMLVJzv6EyE +fmJxJi5rmyai9qGwDo7iHg4BrObGre96KCpl+g91O6I +-> ssh-ed25519 jPowng INA6EZdy4J1p3QY5mfVOQXiLdOjIDaZR+CZMP+GfkXM +8Nf5soaxY5SEzeJca5kaJkx7ByOvc4NkJVetB7wpEmo +-> xjK'w-grease +f5v0cvlt4JbHlAwDOob86qOInWdlN/oohTg +--- NTGv4rr+MhJ/YeZhVHOjoS1V+zCHFf2itJYfK36R+wE +š×—®JÚ dõ– oªê'YFUŸ@ +r7”ã“_N$‰ÿ–è‡>‚¡ê]hq»-¨FÛ°qXÿ?Î| ?µÊ \ No newline at end of file diff --git a/modules/secrets/paperless/secret-key.age b/modules/secrets/paperless/secret-key.age new file mode 100644 index 0000000..eae5c56 --- /dev/null +++ b/modules/secrets/paperless/secret-key.age @@ -0,0 +1,10 @@ +age-encryption.org/v1 +-> ssh-ed25519 cKojmg tZwn2usN6K62oS4vBa6boh9zEp/+cS4chP8boXG6SH4 +Fr3kV8gUDoiDqMxPYWsHyww8umYhQEKhqbVBiVw5NeI +-> ssh-ed25519 jPowng wRbJl4G85obH/GluQBBsXE7MOvooEui65eqHfurvuQs +KqVZMBSyHhkayEdwI6ocmA4qhHY9zYJvg1CEKM1SOa0 +-> 2E"/OFW-grease o Qp3HFe^ +bGhCNicPqt7txqxUiEWXCFs1OuQLqOqHmjHSqYQv919dqYep/xBXzi/aRf3dsdvh +TCJCTvZG31Qxvikp +--- xKJGbdVp+Z5h0vCBleSF2zYYYd2S5i0y4szNqjRwrDY +Tª /N¯¨¹i7m4‚#³MhiñP¹šÒÞ›Á¥-ÏgI÷ñ±%@E†(›iÿ7·ý©ýYg¦k±´"+㸠Àª(þ]o¨¸–ý†ð@báÊÞ§+Ï[‚Y"ÿ‘ÌBóóCR[ >-Ë.4d…¤b9v \ No newline at end of file diff --git a/modules/secrets/podgrab/password.age b/modules/secrets/podgrab/password.age new file mode 100644 index 0000000..90e2501 --- /dev/null +++ b/modules/secrets/podgrab/password.age @@ -0,0 +1,9 @@ +age-encryption.org/v1 +-> ssh-ed25519 cKojmg 8rcBI7fYHuA3jO6EzJNFaAj2niIApKDt1HQEv61AKTs +ANxkIX/CeI7t7Zqp6wmjt/D194Z+xpeiidb+qvYzoQU +-> ssh-ed25519 jPowng oruewwTM9X/HjjcmOPcQVdp02rQBlgJPdzvlAffs3T0 +MrO0kaNhjgOkNHuz3NrIMWXNrXOHH9dT/Fk6hoQNKyY +-> COK%H7-grease +6yfI90QurOKlM+kgpW8KZ/iBzDYD9yhNmjG1LQ +--- uArz8eHg8sLO0sdlkM6cELFh+FHiI5BrM0+iXJxxiDo +¿vývû´ÊNÊbæ@Ÿ¡ÂFÛMMíYËÆíÌ&‰’/%¤¹Ñm¨®ØtÁÖ“ªd†h„­|¡ðŒß©8¼Ž Ú½¨9‚®Cã¯/Å \ No newline at end of file diff --git a/modules/secrets/secrets.nix b/modules/secrets/secrets.nix new file mode 100644 index 0000000..1622d95 --- /dev/null +++ b/modules/secrets/secrets.nix @@ -0,0 +1,66 @@ +let + # FIXME: read them from directories + ambroisie = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMIVd6Oh08iUNb1vTULbxGpevnh++wxsWW9wqhaDryIq ambroisie@agenix"; + users = [ ambroisie ]; + + porthos = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICGzznQ3LSmBYHx6fXthgMDiTcU5i/Nvj020SbmhzAFb root@porthos"; + machines = [ porthos ]; + + all = users ++ machines; +in +{ + "acme/dns-key.age".publicKeys = all; + + "backup/password.age".publicKeys = all; + "backup/credentials.age".publicKeys = all; + + "drone/gitea.age".publicKeys = all; + "drone/secret.age".publicKeys = all; + "drone/ssh/private-key.age".publicKeys = all; + + "gitea/mail-password.age" = { + owner = "git"; + publicKeys = all; + }; + + "lohr/secret.age".publicKeys = all; + "lohr/ssh-key.age".publicKeys = all; + + "matrix/mail.age" = { + owner = "matrix-synapse"; + publicKeys = all; + }; + "matrix/secret.age".publicKeys = all; + + "miniflux/credentials.age".publicKeys = all; + + "monitoring/password.age" = { + owner = "grafana"; + publicKeys = all; + }; + + "nextcloud/password.age" = { + # Must be readable by the service + owner = "nextcloud"; + publicKeys = all; + }; + + "paperless/password.age".publicKeys = all; + "paperless/secret-key.age".publicKeys = all; + + "podgrab/password.age".publicKeys = all; + + "sso/auth-key.age".publicKeys = all; + "sso/ambroisie/password-hash.age".publicKeys = all; + "sso/ambroisie/totp-secret.age".publicKeys = all; + + "transmission/credentials.age".publicKeys = all; + + "users/ambroisie/hashed-password.age".publicKeys = all; + "users/root/hashed-password.age".publicKeys = all; + + "wireguard/aramis/private-key.age".publicKeys = all; + "wireguard/milady/private-key.age".publicKeys = all; + "wireguard/porthos/private-key.age".publicKeys = all; + "wireguard/richelieu/private-key.age".publicKeys = all; +} diff --git a/modules/secrets/sso/ambroisie/password-hash.age b/modules/secrets/sso/ambroisie/password-hash.age new file mode 100644 index 0000000..10d9eaa Binary files /dev/null and b/modules/secrets/sso/ambroisie/password-hash.age differ diff --git a/modules/secrets/sso/ambroisie/totp-secret.age b/modules/secrets/sso/ambroisie/totp-secret.age new file mode 100644 index 0000000..c5ce19b Binary files /dev/null and b/modules/secrets/sso/ambroisie/totp-secret.age differ diff --git a/modules/secrets/sso/auth-key.age b/modules/secrets/sso/auth-key.age new file mode 100644 index 0000000..4e05b15 Binary files /dev/null and b/modules/secrets/sso/auth-key.age differ diff --git a/modules/secrets/sso/default.nix b/modules/secrets/sso/default.nix new file mode 100644 index 0000000..e65a55b --- /dev/null +++ b/modules/secrets/sso/default.nix @@ -0,0 +1,21 @@ +{ lib }: +let + inherit (lib) fileContents; + importUser = (user: { + # bcrypt hashed: `htpasswd -BnC 10 ""` + passwordHash = fileContents (./. + "/${user}/password-hash.txt"); + # base32 encoded: `printf '' | base32 | tr -d =` + totpSecret = fileContents (./. + "/${user}/totp-secret.txt"); + }); +in +{ + auth_key = fileContents ./auth-key.txt; + + users = lib.flip lib.genAttrs importUser [ + "ambroisie" + ]; + + groups = { + root = [ "ambroisie" ]; + }; +} diff --git a/modules/secrets/transmission/credentials.age b/modules/secrets/transmission/credentials.age new file mode 100644 index 0000000..4f407fa --- /dev/null +++ b/modules/secrets/transmission/credentials.age @@ -0,0 +1,10 @@ +age-encryption.org/v1 +-> ssh-ed25519 cKojmg mP2H3PWJN6Pv3q6C2wci3KnXjtFAIiuGy0YH0sGIy2g +f43QqyUQfTYznszub47kgc2Mz95zVScTDkwnG3INi9U +-> ssh-ed25519 jPowng fENbu7+FZ1mnQQHQCLm1spLHmsQGlRoJResUJtGzYkY +hX+AqCkLCca6m/aKtGCThi7/mCCz/TZQNJNOlOmlqyA +-> J<-grease +n7+CPRr4oazWnE7yzpJN2ZAI4QrGsAerloP4wNeebjQDx8+IxJq1JE0g3Yi0RxzN +chDccuSPLYk45Ov+SD/qqqFZlQ +--- p81HYw3LFj+qz2kiZsDcevM4ZBfvN743P9Jdi7J9XkM +‚¢ìÛ±S·7 ‘ý£÷ÜãV»»Bðßâø±³ˆ¶ïO‰lEt˜‹Á…šqý·_D,PºVFp\æ"AM}èg?ÿÝ/\²Ä;ùy’ ¬Óš(ÑSñKË \ No newline at end of file diff --git a/modules/secrets/users/ambroisie/hashed-password.age b/modules/secrets/users/ambroisie/hashed-password.age new file mode 100644 index 0000000..09a80f4 --- /dev/null +++ b/modules/secrets/users/ambroisie/hashed-password.age @@ -0,0 +1,9 @@ +age-encryption.org/v1 +-> ssh-ed25519 cKojmg vOaL2ZKsFEjX9mzQvw8Je7x2Dq8cMhrZEyBTXpH4QnE +HXO4fbWdJsbsRmGq0IYzq8/szObxzpsGfQNNTJ4vNzg +-> ssh-ed25519 jPowng WPxg0pP6O3ZS4dPc1WcDvzig22Fylk3mR/W9STaWbW4 +GuhFwt7M5Lc38q2LC/0eul0yP60UxmWwi9I8ToHv7bE +-> :;V8\-grease ZC#7~eR# P<'e?vI3 9R +lZlb44QiAaIxd0SYiRNT/QRnxxUt7npbksg +--- 9xv4lt8IcGR8jP0UcKYYnTuh1Ix/pqXgDmevkTH9j1A +Ï]ºcÓ3óxí wÿ'ã ` ùhçÒ=X¨í·¢Ç‘g3ÆÆÄ]~ËôÞqÙ.XnÄa*€±W:–¸±,â©z®vyzñI¦æ }ÂDO=`êw“ñõ¹ˆ7:™ù“ÐRx•5$¨Ö6:ö¨´"õ,HM„"_ëÞòMÛMƒœˆBJe‰ùFá \ No newline at end of file diff --git a/modules/secrets/users/root/hashed-password.age b/modules/secrets/users/root/hashed-password.age new file mode 100644 index 0000000..14986f1 Binary files /dev/null and b/modules/secrets/users/root/hashed-password.age differ diff --git a/modules/secrets/wireguard/aramis/private-key.age b/modules/secrets/wireguard/aramis/private-key.age new file mode 100644 index 0000000..d790b64 Binary files /dev/null and b/modules/secrets/wireguard/aramis/private-key.age differ diff --git a/secrets/wireguard/default.nix b/modules/secrets/wireguard/default.nix similarity index 100% rename from secrets/wireguard/default.nix rename to modules/secrets/wireguard/default.nix diff --git a/modules/secrets/wireguard/milady/private-key.age b/modules/secrets/wireguard/milady/private-key.age new file mode 100644 index 0000000..fb84f91 --- /dev/null +++ b/modules/secrets/wireguard/milady/private-key.age @@ -0,0 +1,9 @@ +age-encryption.org/v1 +-> ssh-ed25519 cKojmg gWB20jfimPCJHYjqxBSHYkL9Z/kGZ23dRu4PHp7oJj8 +z3dBymvgrGNtIXe3yQAzpm36uExPmD7DKjU6mMNw99U +-> ssh-ed25519 jPowng aeWv6an+PmWRuk2eHOQhF7jvmld1I5p2LbSmehjUBBw +Rn+ApMvZlO0ji6TCakCUc+1jK762UxOqVanmCsjB+80 +-> jDh})['\-grease |Y6J(8{ +v.7nKx +WID+ZDtsOlPI0AW8ROvXH1s +--- ZlSk2uv95UoKi5D94+tiQdZyxCVv6dlj6ajwYeDzmp0 +çön“¯`Wáø¸öm!Q3]ñËQ}}ý†ŽB­y— €kÛuÐìçÝÆ€EÉ^…zO‡Ö[ÕV ¨p šfâøÀ>¡Ä”ÌÌÖî \ No newline at end of file diff --git a/modules/secrets/wireguard/porthos/private-key.age b/modules/secrets/wireguard/porthos/private-key.age new file mode 100644 index 0000000..4abe1e5 --- /dev/null +++ b/modules/secrets/wireguard/porthos/private-key.age @@ -0,0 +1,10 @@ +age-encryption.org/v1 +-> ssh-ed25519 cKojmg +WwRpd2MzycutQFXyLsr2+GzSgF67Z6UuvyqYZaLd3w +sppt8HzaZP3yxnvnhzjl18Trnz8g3VyXJ6CaVBWd7jA +-> ssh-ed25519 jPowng wanoqGB7T8bim/WZ4IAYViFQoGzaIZSgeoTr3YKpeTY +ihDAdGa1XVW/qQz40V1v7a7iK7tu0EHMa7ayIogpcRw +-> l-grease |PIcZ NIr >0;* +4o8o0bevQZ6uDSx1WxxlDCURbFCM+yK1XPdrb9aztCSvG2a+ne78E42l5rBcoH7I +m51A8uWS4nSj36N/76v6K4kelxKzWUg +--- O6cGbTAVbDcdmPHf7UzfZiyiRtu1yfL4sBI+CkJA1qw +ýqýÕ$ò`¿w'èS“X¸]¥á÷ø®úî…?¤6‹Ð/ÆN(Bžò N«a”.ÿ HŽ7¿í•Iú÷Àoz‡/4:sK",7J \ No newline at end of file diff --git a/modules/secrets/wireguard/richelieu/private-key.age b/modules/secrets/wireguard/richelieu/private-key.age new file mode 100644 index 0000000..e796688 --- /dev/null +++ b/modules/secrets/wireguard/richelieu/private-key.age @@ -0,0 +1,10 @@ +age-encryption.org/v1 +-> ssh-ed25519 cKojmg rYhrpoTaFjLBGtbCXxEK7jZa+KnriEV/kWViIEjmuQs +jHMSjxKIIqjUnpAcEo3JgsieI1iiA5/gKEx8+QFhDgY +-> ssh-ed25519 jPowng 6sQQFvSbWdjgDYSKmJ/CBG+BTzxFghX4SaJ4GyACKWc +OABJuh+Ta8q+G0onF/9bz3xxv4zTlHYlF4AjC5P6Y6I +-> xwW|#D`-grease $xYH C m8lBk9 +OBqgvLNIurE0qNaSB7dO2/6dQkVXeLgf/3l9gGlRJ6ynhqwmbXOUa0vyj+OBz27O +uI97+0y1TFAs3HN0Y8nj8LrwsafbDENu99JuVow2OuLKeSqc7sxOQQ +--- 9filSHStPTJJGDLY7AWzIXu/6tK4X0okT522sc4OJTc +M{²ûô˜¿$¹:NÙÐ[ݶ¬2xy8&äJ_{RÜLXü`W‡•€‡Í»xÑ*Pr`¾U²pøŽJÉ”øêF#åYÔý×òXæP”S s \ No newline at end of file diff --git a/services/adblock.nix b/modules/services/adblock/default.nix similarity index 63% rename from services/adblock.nix rename to modules/services/adblock/default.nix index 23d63c8..45e4d6e 100644 --- a/services/adblock.nix +++ b/modules/services/adblock/default.nix @@ -45,21 +45,28 @@ in services.unbound = { enable = true; - allowedAccess = [ - "127.0.0.0/24" - "${wgCfg.net.v4.subnet}.0/${toString wgCfg.net.v4.mask}" - "${wgCfg.net.v6.subnet}::0/${toString wgCfg.net.v6.mask}" - ]; + settings = { + server = { + access-control = [ + "127.0.0.0/24 allow" + "${wgCfg.net.v4.subnet}.0/${toString wgCfg.net.v4.mask} allow" + "${wgCfg.net.v6.subnet}::0/${toString wgCfg.net.v6.mask} allow" + ]; - inherit (cfg) forwardAddresses interfaces; + interface = cfg.interfaces; - extraConfig = '' - so-reuseport: yes - tls-cert-bundle: /etc/ssl/certs/ca-certificates.crt - tls-upstream: yes + so-reuseport = true; + tls-cert-bundle = "/etc/ssl/certs/ca-certificates.crt"; + tls-upstream = true; - include: "${pkgs.ambroisie.unbound-zones-adblock}/hosts" - ''; + include = "${pkgs.ambroisie.unbound-zones-adblock}/hosts"; + }; + + forward-zone = [{ + name = "."; + forward-addr = cfg.forwardAddresses; + }]; + }; }; }; } diff --git a/services/backup.nix b/modules/services/backup/default.nix similarity index 92% rename from services/backup.nix rename to modules/services/backup/default.nix index da45b5e..ff0fc7f 100644 --- a/services/backup.nix +++ b/modules/services/backup/default.nix @@ -92,13 +92,13 @@ in services.restic.backups.backblaze = { # Take care of included and excluded files paths = cfg.paths; - extraOptions = with builtins; with lib;[ - (optionalString ((length cfg.exclude) != 0) excludeArg) - ]; + extraBackupArgs = [ "--verbose=2" ] + ++ lib.optional (builtins.length cfg.exclude != 0) excludeArg + ; # Take care of creating the repository if it doesn't exist initialize = true; - # Hijack S3-related env to give B2 API key - s3CredentialsFile = cfg.credentialsFile; + # give B2 API key securely + environmentFile = cfg.credentialsFile; inherit (cfg) passwordFile pruneOpts timerConfig repository; }; diff --git a/modules/services/blog/default.nix b/modules/services/blog/default.nix new file mode 100644 index 0000000..9149917 --- /dev/null +++ b/modules/services/blog/default.nix @@ -0,0 +1,33 @@ +# My blog setup +{ config, lib, ... }: +let + cfg = config.my.services.blog; + domain = config.networking.domain; + + makeHostInfo = subdomain: { + inherit subdomain; + root = "/var/www/${subdomain}"; + }; + + hostsInfo = map makeHostInfo [ "cv" "dev" "key" ]; +in +{ + options.my.services.blog = { + enable = lib.mkEnableOption "Blog hosting"; + }; + + config = lib.mkIf cfg.enable { + services.nginx.virtualHosts = { + # This is not a subdomain, cannot use my nginx wrapper module + ${domain} = { + forceSSL = true; + useACMEHost = domain; + root = "/var/www/blog"; + default = true; # Redirect to my blog + }; + }; + + # Those are all subdomains, no problem + my.services.nginx.virtualHosts = hostsInfo; + }; +} diff --git a/services/calibre-web.nix b/modules/services/calibre-web/default.nix similarity index 73% rename from services/calibre-web.nix rename to modules/services/calibre-web/default.nix index d4d7ece..e6ba10d 100644 --- a/services/calibre-web.nix +++ b/modules/services/calibre-web/default.nix @@ -1,8 +1,6 @@ { config, lib, ... }: let cfg = config.my.services.calibre-web; - domain = config.networking.domain; - calibreDomain = "library.${domain}"; in { options.my.services.calibre-web = with lib; { @@ -39,16 +37,19 @@ in }; }; - services.nginx.virtualHosts."${calibreDomain}" = { - forceSSL = true; - useACMEHost = domain; + # Set-up media group + users.groups.media = { }; - locations."/".proxyPass = "http://127.0.0.1:${toString cfg.port}/"; - }; + my.services.nginx.virtualHosts = [ + { + subdomain = "library"; + inherit (cfg) port; + } + ]; my.services.backup = { paths = [ - "/var/lib/calibre-web" # For `app.db` and `gdrive.db` + "/var/lib/${config.services.calibre-web.dataDir}" # For `app.db` and `gdrive.db` cfg.libraryPath ]; }; diff --git a/modules/services/default.nix b/modules/services/default.nix new file mode 100644 index 0000000..a5d129b --- /dev/null +++ b/modules/services/default.nix @@ -0,0 +1,35 @@ +{ ... }: + +{ + imports = [ + ./adblock + ./backup + ./blog + ./calibre-web + ./drone + ./flood + ./gitea + ./grocy + ./indexers + ./jellyfin + ./lohr + ./matrix + ./miniflux + ./monitoring + ./navidrome + ./nextcloud + ./nginx + ./paperless + ./pirate + ./podgrab + ./postgresql-backup + ./postgresql + ./quassel + ./rss-bridge + ./sabnzbd + ./ssh-server + ./tlp + ./transmission + ./wireguard + ]; +} diff --git a/modules/services/drone/default.nix b/modules/services/drone/default.nix new file mode 100644 index 0000000..79c48dd --- /dev/null +++ b/modules/services/drone/default.nix @@ -0,0 +1,44 @@ +# A docker-based CI/CD system +# +# Inspired by [1] +# [1]: https://github.com/Mic92/dotfiles/blob/master/nixos/eve/modules/drone.nix +{ lib, ... }: +{ + imports = [ + ./runner-docker + ./runner-exec + ./server + ]; + + options.my.services.drone = with lib; { + enable = mkEnableOption "Drone CI"; + runners = mkOption { + type = with types; listOf (enum [ "exec" "docker" ]); + default = [ ]; + example = [ "exec" "docker" ]; + description = "Types of runners to enable"; + }; + admin = mkOption { + type = types.str; + default = "ambroisie"; + example = "admin"; + description = "Name of the admin user"; + }; + port = mkOption { + type = types.port; + default = 3030; + example = 8080; + description = "Internal port of the Drone UI"; + }; + secretFile = mkOption { + type = types.str; + example = "/run/secrets/drone-gitea.env"; + description = "Secrets to inject into Drone server"; + }; + sharedSecretFile = mkOption { + type = types.str; + example = "/run/secrets/drone-rpc.env"; + description = "Shared RPC secret to inject into server and runners"; + }; + }; +} diff --git a/modules/services/drone/runner-docker/default.nix b/modules/services/drone/runner-docker/default.nix new file mode 100644 index 0000000..0f2e3b3 --- /dev/null +++ b/modules/services/drone/runner-docker/default.nix @@ -0,0 +1,44 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.services.drone; + hasRunner = (name: builtins.elem name cfg.runners); + dockerPkg = pkgs.drone-runner-docker; +in +{ + config = lib.mkIf (cfg.enable && hasRunner "docker") { + systemd.services.drone-runner-docker = { + wantedBy = [ "multi-user.target" ]; + after = [ "docker.socket" ]; # Needs the socket to be available + # might break deployment + restartIfChanged = false; + confinement.enable = true; + serviceConfig = { + Environment = [ + "DRONE_SERVER_HOST=drone.${config.networking.domain}" + "DRONE_SERVER_PROTO=https" + "DRONE_RUNNER_CAPACITY=10" + "CLIENT_DRONE_RPC_HOST=127.0.0.1:${toString cfg.port}" + ]; + BindPaths = [ + "/var/run/docker.sock" + ]; + EnvironmentFile = [ + cfg.sharedSecretFile + ]; + ExecStart = "${dockerPkg}/bin/drone-runner-docker"; + User = "drone-runner-docker"; + Group = "drone-runner-docker"; + }; + }; + + # Make sure it is activated in that case + virtualisation.docker.enable = true; + + users.users.drone-runner-docker = { + isSystemUser = true; + group = "drone-runner-docker"; + extraGroups = [ "docker" ]; # Give access to the daemon + }; + users.groups.drone-runner-docker = { }; + }; +} diff --git a/modules/services/drone/runner-exec/default.nix b/modules/services/drone/runner-exec/default.nix new file mode 100644 index 0000000..6c776b4 --- /dev/null +++ b/modules/services/drone/runner-exec/default.nix @@ -0,0 +1,68 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.services.drone; + hasRunner = (name: builtins.elem name cfg.runners); + execPkg = pkgs.drone-runner-exec; +in +{ + config = lib.mkIf (cfg.enable && hasRunner "exec") { + systemd.services.drone-runner-exec = { + wantedBy = [ "multi-user.target" ]; + # might break deployment + restartIfChanged = false; + confinement.enable = true; + confinement.packages = with pkgs; [ + git + gnutar + bash + nixUnstable + gzip + ]; + path = with pkgs; [ + git + gnutar + bash + nixUnstable + gzip + ]; + serviceConfig = { + Environment = [ + "DRONE_SERVER_HOST=drone.${config.networking.domain}" + "DRONE_SERVER_PROTO=https" + "DRONE_RUNNER_CAPACITY=10" + "CLIENT_DRONE_RPC_HOST=127.0.0.1:${toString cfg.port}" + "NIX_REMOTE=daemon" + "PAGER=cat" + ]; + BindPaths = [ + "/nix/var/nix/daemon-socket/socket" + "/run/nscd/socket" + ]; + BindReadOnlyPaths = [ + "/etc/resolv.conf:/etc/resolv.conf" + "/etc/resolvconf.conf:/etc/resolvconf.conf" + "/etc/passwd:/etc/passwd" + "/etc/group:/etc/group" + "/nix/var/nix/profiles/system/etc/nix:/etc/nix" + "${config.environment.etc."ssl/certs/ca-certificates.crt".source}:/etc/ssl/certs/ca-certificates.crt" + "${config.environment.etc."ssh/ssh_known_hosts".source}:/etc/ssh/ssh_known_hosts" + "/etc/machine-id" + # channels are dynamic paths in the nix store, therefore we need to bind mount the whole thing + "/nix/" + ]; + EnvironmentFile = [ + cfg.sharedSecretFile + ]; + ExecStart = "${execPkg}/bin/drone-runner-exec"; + User = "drone-runner-exec"; + Group = "drone-runner-exec"; + }; + }; + + users.users.drone-runner-exec = { + isSystemUser = true; + group = "drone-runner-exec"; + }; + users.groups.drone-runner-exec = { }; + }; +} diff --git a/modules/services/drone/server/default.nix b/modules/services/drone/server/default.nix new file mode 100644 index 0000000..1202010 --- /dev/null +++ b/modules/services/drone/server/default.nix @@ -0,0 +1,56 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.services.drone; +in +{ + config = lib.mkIf cfg.enable { + systemd.services.drone-server = { + wantedBy = [ "multi-user.target" ]; + after = [ "postgresql.service" ]; + serviceConfig = { + EnvironmentFile = [ + cfg.secretFile + cfg.sharedSecretFile + ]; + Environment = [ + "DRONE_DATABASE_DATASOURCE=postgres:///drone?host=/run/postgresql" + "DRONE_SERVER_HOST=drone.${config.networking.domain}" + "DRONE_SERVER_PROTO=https" + "DRONE_DATABASE_DRIVER=postgres" + "DRONE_SERVER_PORT=:${toString cfg.port}" + "DRONE_USER_CREATE=username:${cfg.admin},admin:true" + "DRONE_JSONNET_ENABLED=true" + "DRONE_STARLARK_ENABLED=true" + ]; + ExecStart = "${pkgs.drone}/bin/drone-server"; + User = "drone"; + Group = "drone"; + }; + }; + + users.users.drone = { + isSystemUser = true; + createHome = true; + group = "drone"; + }; + users.groups.drone = { }; + + services.postgresql = { + enable = true; + ensureDatabases = [ "drone" ]; + ensureUsers = [{ + name = "drone"; + ensurePermissions = { + "DATABASE drone" = "ALL PRIVILEGES"; + }; + }]; + }; + + my.services.nginx.virtualHosts = [ + { + subdomain = "drone"; + inherit (cfg) port; + } + ]; + }; +} diff --git a/modules/services/flood/default.nix b/modules/services/flood/default.nix new file mode 100644 index 0000000..ae8e219 --- /dev/null +++ b/modules/services/flood/default.nix @@ -0,0 +1,50 @@ +# A nice UI for various torrent clients +{ config, lib, pkgs, ... }: +let + cfg = config.my.services.flood; +in +{ + options.my.services.flood = with lib; { + enable = mkEnableOption "Flood UI"; + + port = mkOption { + type = types.port; + default = 9092; + example = 3000; + description = "Internal port for Flood UI"; + }; + + stateDir = mkOption { + type = types.str; + default = "flood"; + example = "floodUI"; + description = "Directory under `/var/run` for storing Flood's files"; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services.flood = { + description = "Flood torrent UI"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + ExecStart = lib.concatStringsSep " " [ + "${pkgs.flood}/bin/flood" + "--port ${builtins.toString cfg.port}" + "--rundir /var/lib/${cfg.stateDir}" + ]; + DynamicUser = true; + StateDirectory = cfg.stateDir; + ReadWritePaths = ""; + }; + }; + + my.services.nginx.virtualHosts = [ + { + subdomain = "flood"; + inherit (cfg) port; + } + ]; + }; +} diff --git a/modules/services/gitea/default.nix b/modules/services/gitea/default.nix new file mode 100644 index 0000000..9c443f0 --- /dev/null +++ b/modules/services/gitea/default.nix @@ -0,0 +1,130 @@ +# A low-ressource, full-featured git forge. +{ config, lib, ... }: +let + cfg = config.my.services.gitea; +in +{ + options.my.services.gitea = with lib; { + enable = mkEnableOption "Gitea"; + port = mkOption { + type = types.port; + default = 3042; + example = 8080; + description = "Internal port"; + }; + mail = { + enable = mkEnableOption { + description = "mailer configuration"; + }; + host = mkOption { + type = types.str; + example = "smtp.example.com:465"; + description = "Host for the mail account"; + }; + user = mkOption { + type = types.str; + example = "gitea@example.com"; + description = "User for the mail account"; + }; + passwordFile = mkOption { + type = types.str; + example = "/run/secrets/gitea-mail-password.txt"; + description = "Password for the mail account"; + }; + type = mkOption { + type = types.str; + default = "smtp"; + example = "smtp"; + description = "Password for the mail account"; + }; + tls = mkOption { + type = types.bool; + default = true; + example = false; + description = "Use TLS for connection"; + }; + }; + }; + + config = lib.mkIf cfg.enable { + services.gitea = + let + inherit (config.networking) domain; + giteaDomain = "gitea.${domain}"; + in + { + enable = true; + + appName = "Ambroisie's forge"; + httpPort = cfg.port; + domain = giteaDomain; + rootUrl = "https://${giteaDomain}"; + + user = "git"; + lfs.enable = true; + + useWizard = false; + + database = { + type = "postgres"; # Automatic setup + user = "git"; # User needs to be the same as gitea user + }; + + # NixOS module uses `gitea dump` to backup repositories and the database, + # but it produces a single .zip file that's not very backup friendly. + # I configure my backup system manually below. + dump.enable = false; + + mailerPasswordFile = lib.mkIf cfg.mail.enable cfg.mail.passwordFile; + + settings = { + mailer = lib.mkIf cfg.mail.enable { + ENABLED = true; + HOST = cfg.mail.host; + FROM = cfg.mail.user; + USER = cfg.mail.user; + MAILER_TYPE = cfg.mail.type; + IS_TLS_ENABLED = cfg.mail.tls; + }; + + service = { + DISABLE_REGISTRATION = true; + }; + + session = { + # only send cookies via HTTPS + COOKIE_SECURE = true; + }; + }; + }; + + users.users.git = { + description = "Gitea Service"; + home = config.services.gitea.stateDir; + useDefaultShell = true; + group = "git"; + + # The service for gitea seems to hardcode the group as + # gitea, so, uh, just in case? + extraGroups = [ "gitea" ]; + + isSystemUser = true; + }; + users.groups.git = { }; + + # Proxy to Gitea + my.services.nginx.virtualHosts = [ + { + subdomain = "gitea"; + inherit (cfg) port; + } + ]; + + my.services.backup = { + paths = [ + config.services.gitea.lfs.contentDir + config.services.gitea.repositoryRoot + ]; + }; + }; +} diff --git a/modules/services/grocy/default.nix b/modules/services/grocy/default.nix new file mode 100644 index 0000000..87927d6 --- /dev/null +++ b/modules/services/grocy/default.nix @@ -0,0 +1,40 @@ +# Groceries and household management +{ config, lib, ... }: +let + cfg = config.my.services.grocy; + grocyDomain = "grocy.${config.networking.domain}"; +in +{ + options.my.services.grocy = with lib; { + enable = mkEnableOption "Grocy household ERP"; + }; + + config = lib.mkIf cfg.enable { + services.grocy = { + enable = true; + + # The service sets up the reverse proxy automatically + hostName = grocyDomain; + + # Configure SSL by hand + nginx = { + enableSSL = false; + }; + + settings = { + currency = "EUR"; + culture = "en"; + calendar = { + # Start on Monday + firstDayOfWeek = 1; + showWeekNumber = true; + }; + }; + }; + + services.nginx.virtualHosts."${grocyDomain}" = { + forceSSL = true; + useACMEHost = config.networking.domain; + }; + }; +} diff --git a/modules/services/indexers/default.nix b/modules/services/indexers/default.nix new file mode 100644 index 0000000..66f1604 --- /dev/null +++ b/modules/services/indexers/default.nix @@ -0,0 +1,65 @@ +# Torrent and usenet meta-indexers +{ config, lib, ... }: +let + cfg = config.my.services.indexers; + + jackettPort = 9117; + nzbhydraPort = 5076; + prowlarrPort = 9696; +in +{ + options.my.services.indexers = with lib; { + jackett.enable = mkEnableOption "Jackett torrent meta-indexer"; + nzbhydra.enable = mkEnableOption "NZBHydra2 usenet meta-indexer"; + prowlarr.enable = mkEnableOption "Prowlarr torrent & usenet meta-indexer"; + }; + + config = lib.mkMerge [ + (lib.mkIf cfg.jackett.enable { + services.jackett = { + enable = true; + }; + + # Jackett wants to eat *all* my RAM if left to its own devices + systemd.services.jackett = { + serviceConfig = { + MemoryHigh = "15%"; + MemoryMax = "25%"; + }; + }; + + my.services.nginx.virtualHosts = [ + { + subdomain = "jackett"; + port = jackettPort; + } + ]; + }) + + (lib.mkIf cfg.nzbhydra.enable { + services.nzbhydra2 = { + enable = true; + }; + + my.services.nginx.virtualHosts = [ + { + subdomain = "nzbhydra"; + port = nzbhydraPort; + } + ]; + }) + + (lib.mkIf cfg.prowlarr.enable { + services.prowlarr = { + enable = true; + }; + + my.services.nginx.virtualHosts = [ + { + subdomain = "prowlarr"; + port = prowlarrPort; + } + ]; + }) + ]; +} diff --git a/modules/services/jellyfin/default.nix b/modules/services/jellyfin/default.nix new file mode 100644 index 0000000..2fcf51e --- /dev/null +++ b/modules/services/jellyfin/default.nix @@ -0,0 +1,39 @@ +# A FLOSS media server +{ config, lib, ... }: +let + cfg = config.my.services.jellyfin; +in +{ + options.my.services.jellyfin = { + enable = lib.mkEnableOption "Jellyfin Media Server"; + }; + + config = lib.mkIf cfg.enable { + services.jellyfin = { + enable = true; + group = "media"; + }; + + # Set-up media group + users.groups.media = { }; + + my.services.nginx.virtualHosts = [ + { + subdomain = "jellyfin"; + port = 8096; + extraConfig = { + locations."/" = { + extraConfig = '' + proxy_buffering off; + ''; + }; + # Too bad for the repetition... + locations."/socket" = { + proxyPass = "http://127.0.0.1:8096/"; + proxyWebsockets = true; + }; + }; + } + ]; + }; +} diff --git a/services/lohr.nix b/modules/services/lohr/default.nix similarity index 61% rename from services/lohr.nix rename to modules/services/lohr/default.nix index 57f4feb..f43bc40 100644 --- a/services/lohr.nix +++ b/modules/services/lohr/default.nix @@ -4,10 +4,10 @@ let cfg = config.my.services.lohr; settingsFormat = pkgs.formats.yaml { }; - domain = config.networking.domain; - lohrDomain = "lohr.${config.networking.domain}"; - lohrPkg = pkgs.ambroisie.lohr; + + lohrStateDirectory = "lohr"; + lohrHome = "/var/lib/lohr/"; in { options.my.services.lohr = with lib; { @@ -37,6 +37,15 @@ in example = "/run/secrets/lohr.env"; description = "Shared secret between lohr and Gitea hook"; }; + + sshKeyFile = mkOption { + type = with types; nullOr str; + default = null; + example = "/run/secrets/lohr/ssh-key"; + description = '' + The ssh key that should be used by lohr to mirror repositories + ''; + }; }; config = lib.mkIf cfg.enable { @@ -49,39 +58,53 @@ in Environment = [ "ROCKET_PORT=${toString cfg.port}" "ROCKET_LOG_LEVEL=normal" - "LOHR_HOME=/var/lib/lohr/" + "LOHR_HOME=${lohrHome}" "LOHR_CONFIG=" ]; + ExecStartPre = lib.mkIf (cfg.sshKeyFile != null) ''+${ + pkgs.writeScript "copy-ssh-key" '' + #!${pkgs.bash}/bin/bash + # Ensure the key is not there + mkdir -p '${lohrHome}/.ssh' + rm -f '${lohrHome}/.ssh/id_ed25519' + + # Move the key into place + cp ${cfg.sshKeyFile} '${lohrHome}/.ssh/id_ed25519' + + # Fix permissions + chown -R lohr:lohr '${lohrHome}/.ssh' + chmod -R 0700 '${lohrHome}/.ssh' + '' + }''; ExecStart = let configFile = settingsFormat.generate "lohr-config.yaml" cfg.setting; in "${lohrPkg}/bin/lohr --config ${configFile}"; - StateDirectory = "lohr"; - WorkingDirectory = "/var/lib/lohr"; + StateDirectory = lohrStateDirectory; + WorkingDirectory = lohrHome; User = "lohr"; Group = "lohr"; }; path = with pkgs; [ git + openssh ]; }; users.users.lohr = { isSystemUser = true; - home = "/var/lib/lohr"; + home = lohrHome; createHome = true; group = "lohr"; }; users.groups.lohr = { }; - services.nginx.virtualHosts."${lohrDomain}" = { - forceSSL = true; - useACMEHost = domain; - - locations."/" = { - proxyPass = "http://127.0.0.1:${toString cfg.port}/"; - }; - }; + my.services.nginx.virtualHosts = [ + { + subdomain = "lohr"; + inherit (cfg) port; + } + ]; }; } diff --git a/modules/services/matrix/bridges.nix b/modules/services/matrix/bridges.nix new file mode 100644 index 0000000..1fa47e8 --- /dev/null +++ b/modules/services/matrix/bridges.nix @@ -0,0 +1,101 @@ +# Matrix bridges, thanks to [1]. +# +# [1]: https://gitlab.com/coffeetables/nix-matrix-appservices/ +{ config, inputs, lib, pkgs, ... }: +let + cfg = config.my.services.matrix.bridges; + domain = config.networking.domain; +in +{ + imports = [ + inputs.matrix-appservices.nixosModules.matrix-appservices + ]; + + options.my.services.matrix.bridges = with lib; { + enable = mkEnableOption "Matrix bridges configuration"; + }; + + config = lib.mkIf cfg.enable { + services.nginx.virtualHosts = { + "matrix.${domain}" = { + locations."/bridges/facebook/login" = { + proxyPass = "http://[::1]:29181"; + }; + }; + }; + + services.matrix-appservices = { + homeserver = "matrix-synapse"; + + homeserverDomain = "belanyi.fr"; + homeserverURL = "https://matrix.belanyi.fr"; + + addRegistrationFiles = true; + + # FIXME: explicitly configure logging through systemd, not log files + # FIXME: register ports to avoid conflicts + services = { + # discord = { + # port = 29180; + # format = "mautrix-go"; + # package = pkgs.mautrix-discord; + # }; + + facebook = { + port = 29181; + format = "mautrix-python"; + package = pkgs.mautrix-facebook; + + settings = { + appservice = { + # Enable login by link + public = { + enabled = true; + prefix = "/bridges/facebook/login"; + external = "https://matrix.${domain}/bridges/facebook/login"; + }; + }; + + bridge = { + # Enable encryption by default + encryption = { + allow = true; + default = true; + allow_key_sharing = true; + + # FIXME: crash loop if not defined explicitly... + verification_levels = { + # Minimum level for which the bridge should send keys to when bridging messages from Telegram to Matrix. + receive = "unverified"; + # Minimum level that the bridge should accept for incoming Matrix messages. + send = "unverified"; + # Minimum level that the bridge should require for accepting key requests. + share = "cross-signed-tofu"; + }; + }; + }; + }; + }; + + whatsapp = { + port = 29182; + format = "mautrix-go"; + package = pkgs.mautrix-whatsapp; + + settings = { + bridge = { + # Create a space for all bridges chat rooms + personal_filtering_spaces = true; + # Enable encryption by default + encryption = { + allow = true; + default = true; + allow_key_sharing = true; + }; + }; + }; + }; + }; + }; + }; +} diff --git a/services/matrix.nix b/modules/services/matrix/default.nix similarity index 73% rename from services/matrix.nix rename to modules/services/matrix/default.nix index 8dc6cad..b3db0ba 100644 --- a/services/matrix.nix +++ b/modules/services/matrix/default.nix @@ -16,19 +16,32 @@ let domain = config.networking.domain; in { + imports = [ + ./bridges.nix + ]; + options.my.services.matrix = with lib; { enable = mkEnableOption "Matrix Synapse"; - secret = mkOption { - type = types.str; - example = "deadbeef"; + + secretFile = mkOption { + type = with types; nullOr str; + default = null; + example = "/var/lib/matrix/shared-secret-config.yaml"; description = "Shared secret to register users"; }; + + mailConfigFile = mkOption { + type = types.str; + example = "/var/lib/matrix/email-config.yaml"; + description = '' + Configuration file for mail setup. + ''; + }; }; config = lib.mkIf cfg.enable { services.postgresql = { enable = true; - package = pkgs.postgresql_12; initialScript = pkgs.writeText "synapse-init.sql" '' CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD 'synapse'; CREATE DATABASE "matrix-synapse" WITH OWNER "matrix-synapse" @@ -41,36 +54,80 @@ in services.matrix-synapse = { enable = true; dataDir = "/var/lib/matrix-synapse"; - server_name = domain; - public_baseurl = "https://matrix.${domain}"; - enable_registration = false; - registration_shared_secret = cfg.secret; + settings = { + server_name = domain; + public_baseurl = "https://matrix.${domain}"; - listeners = [ - # Federation - { - bind_address = "::1"; - port = federationPort.private; - tls = false; # Terminated by nginx. - x_forwarded = true; - resources = [{ names = [ "federation" ]; compress = false; }]; - } + enable_registration = false; + # registration_shared_secret = cfg.secret; # FIXME: use a secret file for this - # Client - { - bind_address = "::1"; - port = clientPort.private; - tls = false; # Terminated by nginx. - x_forwarded = true; - resources = [{ names = [ "client" ]; compress = false; }]; - } - ]; + listeners = [ + # Federation + { + bind_addresses = [ "::1" ]; + port = federationPort.private; + tls = false; # Terminated by nginx. + x_forwarded = true; + resources = [{ names = [ "federation" ]; compress = false; }]; + } + + # Client + { + bind_addresses = [ "::1" ]; + port = clientPort.private; + tls = false; # Terminated by nginx. + x_forwarded = true; + resources = [{ names = [ "client" ]; compress = false; }]; + } + ]; + + account_threepid_delegates = { + msisdn = "https://vector.im"; + }; + + experimental_features = { + spaces_enabled = true; + }; + }; + + extraConfigFiles = [ + cfg.mailConfigFile + ] ++ lib.optional (cfg.secretFile != null) cfg.secretFile; }; + my.services.nginx.virtualHosts = [ + # Element Web app deployment + { + subdomain = "chat"; + root = pkgs.element-web.override { + conf = { + default_server_config = { + "m.homeserver" = { + "base_url" = "https://matrix.${domain}"; + "server_name" = domain; + }; + "m.identity_server" = { + "base_url" = "https://vector.im"; + }; + }; + showLabsSettings = true; + defaultCountryCode = "FR"; # cocorico + roomDirectory = { + "servers" = [ + "matrix.org" + "mozilla.org" + ]; + }; + }; + }; + } + ]; + + # Those are too complicated to use my wrapper... services.nginx.virtualHosts = { "matrix.${domain}" = { - forceSSL = true; + onlySSL = true; useACMEHost = domain; locations = @@ -98,7 +155,7 @@ in # same as above, but listening on the federation port "matrix.${domain}_federation" = rec { - forceSSL = true; + onlySSL = true; serverName = "matrix.${domain}"; useACMEHost = domain; @@ -142,34 +199,6 @@ in return 200 '${builtins.toJSON client}'; ''; }; - - # Element Web app deployment - "chat.${domain}" = { - useACMEHost = domain; - forceSSL = true; - - root = pkgs.element-web.override { - conf = { - default_server_config = { - "m.homeserver" = { - "base_url" = "https://matrix.${domain}"; - "server_name" = domain; - }; - "m.identity_server" = { - "base_url" = "https://vector.im"; - }; - }; - showLabsSettings = true; - defaultCountryCode = "FR"; # cocorico - roomDirectory = { - "servers" = [ - "matrix.org" - "mozilla.org" - ]; - }; - }; - }; - }; }; # For administration tools. diff --git a/modules/services/miniflux/default.nix b/modules/services/miniflux/default.nix new file mode 100644 index 0000000..6d9ffc8 --- /dev/null +++ b/modules/services/miniflux/default.nix @@ -0,0 +1,53 @@ +# A minimalist, opinionated feed reader +{ config, lib, ... }: +let + cfg = config.my.services.miniflux; +in +{ + options.my.services.miniflux = with lib; { + enable = mkEnableOption "Miniflux feed reader"; + + credentialsFiles = mkOption { + type = types.str; + example = "/var/lib/miniflux/creds.env"; + description = '' + Credential file as an 'EnvironmentFile' (see `systemd.exec(5)`) + ''; + }; + + port = mkOption { + type = types.port; + default = 9876; + example = 8080; + description = "Internal port for webui"; + }; + }; + + config = lib.mkIf cfg.enable { + # The service automatically sets up the DB + services.miniflux = { + enable = true; + + adminCredentialsFile = cfg.credentialsFiles; + + config = { + # Virtual hosts settings + BASE_URL = "https://reader.${config.networking.domain}"; + LISTEN_ADDR = "localhost:${toString cfg.port}"; + # I want fast updates + POLLING_FREQUENCY = "30"; + BATCH_SIZE = "50"; + # I am a hoarder + CLEANUP_ARCHIVE_UNREAD_DAYS = "-1"; + CLEANUP_ARCHIVE_READ_DAYS = "-1"; + }; + }; + + my.services.nginx.virtualHosts = [ + { + subdomain = "reader"; + inherit (cfg) port; + } + ]; + }; +} diff --git a/modules/services/monitoring/default.nix b/modules/services/monitoring/default.nix new file mode 100644 index 0000000..ba5adf3 --- /dev/null +++ b/modules/services/monitoring/default.nix @@ -0,0 +1,122 @@ +# Grafana dashboards for all the things! +{ config, lib, pkgs, ... }: +let + cfg = config.my.services.monitoring; +in +{ + options.my.services.monitoring = with lib; { + enable = mkEnableOption "monitoring"; + + grafana = { + port = mkOption { + type = types.port; + default = 9500; + example = 3001; + description = "Internal port"; + }; + + username = mkOption { + type = types.str; + default = "ambroisie"; + example = "admin"; + description = "Admin username"; + }; + + passwordFile = mkOption { + type = types.str; + example = "/var/lib/grafana/password.txt"; + description = "Admin password stored in a file"; + }; + }; + + prometheus = { + port = mkOption { + type = types.port; + default = 9501; + example = 3002; + description = "Internal port"; + }; + + scrapeInterval = mkOption { + type = types.str; + default = "15s"; + example = "1m"; + description = "Scrape interval"; + }; + }; + }; + + config = lib.mkIf cfg.enable { + services.grafana = { + enable = true; + domain = "monitoring.${config.networking.domain}"; + port = cfg.grafana.port; + addr = "127.0.0.1"; # Proxied through Nginx + + security = { + adminUser = cfg.grafana.username; + adminPasswordFile = cfg.grafana.passwordFile; + }; + + provision = { + enable = true; + + datasources = [ + { + name = "Prometheus"; + type = "prometheus"; + url = "http://localhost:${toString cfg.prometheus.port}"; + jsonData = { + timeInterval = cfg.prometheus.scrapeInterval; + }; + } + ]; + + dashboards = [ + { + name = "Node Exporter"; + options.path = pkgs.nur.repos.alarsyo.grafanaDashboards.node-exporter; + disableDeletion = true; + } + ]; + }; + }; + + services.prometheus = { + enable = true; + port = cfg.prometheus.port; + listenAddress = "127.0.0.1"; + + retentionTime = "2y"; + + exporters = { + node = { + enable = true; + enabledCollectors = [ "systemd" ]; + port = 9100; + listenAddress = "127.0.0.1"; + }; + }; + + globalConfig = { + scrape_interval = cfg.prometheus.scrapeInterval; + }; + + scrapeConfigs = [ + { + job_name = config.networking.hostName; + static_configs = [{ + targets = [ "127.0.0.1:${toString config.services.prometheus.exporters.node.port}" ]; + }]; + } + ]; + }; + + my.services.nginx.virtualHosts = [ + { + subdomain = "monitoring"; + inherit (cfg.grafana) port; + } + ]; + }; +} diff --git a/modules/services/navidrome/default.nix b/modules/services/navidrome/default.nix new file mode 100644 index 0000000..6c001fd --- /dev/null +++ b/modules/services/navidrome/default.nix @@ -0,0 +1,57 @@ +# A FLOSS self-hosted, subsonic compatible music server +{ config, lib, pkgs, ... }: +let + cfg = config.my.services.navidrome; +in +{ + options.my.services.navidrome = with lib; { + enable = mkEnableOption "Navidrome Music Server"; + + settings = mkOption { + type = (pkgs.formats.json { }).type; + default = { }; + example = { + "LastFM.ApiKey" = "MYKEY"; + "LastFM.Secret" = "MYSECRET"; + "Spotify.ID" = "MYKEY"; + "Spotify.Secret" = "MYSECRET"; + }; + description = '' + Additional settings. + ''; + }; + + port = mkOption { + type = types.port; + default = 4533; + example = 8080; + description = "Internal port for webui"; + }; + + musicFolder = mkOption { + type = types.str; + example = "/mnt/music/"; + description = "Music folder"; + }; + }; + + config = lib.mkIf cfg.enable { + services.navidrome = { + enable = true; + + settings = cfg.settings // { + Port = cfg.port; + Address = "127.0.0.1"; # Behind reverse proxy, so only loopback + MusicFolder = cfg.musicFolder; + LogLevel = "info"; + }; + }; + + my.services.nginx.virtualHosts = [ + { + subdomain = "music"; + inherit (cfg) port; + } + ]; + }; +} diff --git a/services/nextcloud.nix b/modules/services/nextcloud/default.nix similarity index 66% rename from services/nextcloud.nix rename to modules/services/nextcloud/default.nix index d52e32a..f2d5484 100644 --- a/services/nextcloud.nix +++ b/modules/services/nextcloud/default.nix @@ -2,8 +2,6 @@ { config, lib, pkgs, ... }: let cfg = config.my.services.nextcloud; - domain = config.networking.domain; - nextcloudDomain = "nextcloud.${config.networking.domain}"; in { options.my.services.nextcloud = with lib; { @@ -20,23 +18,26 @@ in example = "admin"; description = "Name of the admin user"; }; - password = mkOption { + passwordFile = mkOption { type = types.str; - example = "password"; - description = "The admin user's password"; + example = "/var/lib/nextcloud/password.txt"; + description = '' + Path to a file containing the admin's password, must be readable by + 'nextcloud' user. + ''; }; }; config = lib.mkIf cfg.enable { services.nextcloud = { enable = true; - package = pkgs.nextcloud21; - hostName = nextcloudDomain; + package = pkgs.nextcloud24; + hostName = "nextcloud.${config.networking.domain}"; home = "/var/lib/nextcloud"; maxUploadSize = cfg.maxSize; config = { adminuser = cfg.admin; - adminpass = cfg.password; # Insecure, but I don't care + adminpassFile = cfg.passwordFile; dbtype = "pgsql"; dbhost = "/run/postgresql"; overwriteProtocol = "https"; # Nginx only allows SSL @@ -59,17 +60,20 @@ in after = [ "postgresql.service" ]; }; - services.nginx.virtualHosts."${nextcloudDomain}" = { + # The service above configures the domain, no need for my wrapper + services.nginx.virtualHosts."nextcloud.${config.networking.domain}" = { forceSSL = true; - useACMEHost = domain; - - locations."/".proxyPass = "http://127.0.0.1:3000/"; + useACMEHost = config.networking.domain; }; my.services.backup = { paths = [ config.services.nextcloud.home ]; + exclude = [ + # image previews can take up a lot of space + "${config.services.nextcloud.home}/data/appdata_*/preview" + ]; }; }; } diff --git a/modules/services/nginx/default.nix b/modules/services/nginx/default.nix new file mode 100644 index 0000000..d99ff2d --- /dev/null +++ b/modules/services/nginx/default.nix @@ -0,0 +1,437 @@ +# A simple abstraction layer for almost all of my services' needs +{ config, lib, pkgs, ... }: +let + cfg = config.my.services.nginx; + + virtualHostOption = with lib; types.submodule { + options = { + subdomain = mkOption { + type = types.str; + example = "dev"; + description = '' + Which subdomain, under config.networking.domain, to use + for this virtual host. + ''; + }; + + port = mkOption { + type = with types; nullOr port; + default = null; + example = 8080; + description = '' + Which port to proxy to, through 127.0.0.1, for this virtual host. + This option is incompatible with `root`. + ''; + }; + + root = mkOption { + type = with types; nullOr path; + default = null; + example = "/var/www/blog"; + description = '' + The root folder for this virtual host. This option is incompatible + with `port`. + ''; + }; + + sso = { + enable = mkEnableOption "SSO authentication"; + }; + + extraConfig = mkOption { + type = types.attrs; # FIXME: forward type of virtualHosts + example = litteralExample '' + { + locations."/socket" = { + proxyPass = "http://127.0.0.1:8096/"; + proxyWebsockets = true; + }; + } + ''; + default = { }; + description = '' + Any extra configuration that should be applied to this virtual host. + ''; + }; + }; + }; +in +{ + imports = [ + ./sso + ]; + + options.my.services.nginx = with lib; { + enable = mkEnableOption "Nginx"; + + acme = { + credentialsFile = mkOption { + type = types.str; + example = "/var/lib/acme/creds.env"; + description = '' + Gandi API key file as an 'EnvironmentFile' (see `systemd.exec(5)`) + ''; + }; + }; + + monitoring = { + enable = my.mkDisableOption "monitoring through grafana and prometheus"; + }; + + virtualHosts = mkOption { + type = types.listOf virtualHostOption; + default = [ ]; + example = litteralExample '' + [ + { + subdomain = "gitea"; + port = 8080; + } + { + subdomain = "dev"; + root = "/var/www/dev"; + } + { + subdomain = "jellyfin"; + port = 8096; + extraConfig = { + locations."/socket" = { + proxyPass = "http://127.0.0.1:8096/"; + proxyWebsockets = true; + }; + }; + } + ] + ''; + description = '' + List of virtual hosts to set-up using default settings. + ''; + }; + + sso = { + authKeyFile = mkOption { + type = types.str; + example = "/var/lib/nginx-sso/auth-key.txt"; + description = '' + Path to the auth key. + ''; + }; + + subdomain = mkOption { + type = types.str; + default = "login"; + example = "auth"; + description = "Which subdomain, to use for SSO."; + }; + + port = mkOption { + type = types.port; + default = 8082; + example = 8080; + description = "Port to use for internal webui."; + }; + + users = mkOption { + type = types.attrsOf (types.submodule { + options = { + passwordHashFile = mkOption { + type = types.str; + example = "/var/lib/nginx-sso/alice/password-hash.txt"; + description = "Path to file containing the user's password hash."; + }; + totpSecretFile = mkOption { + type = types.str; + example = "/var/lib/nginx-sso/alice/totp-secret.txt"; + description = "Path to file containing the user's TOTP secret."; + }; + }; + }); + example = litteralExample '' + { + alice = { + passwordHashFile = "/var/lib/nginx-sso/alice/password-hash.txt"; + totpSecretFile = "/var/lib/nginx-sso/alice/totp-secret.txt"; + }; + } + ''; + description = "Definition of users"; + }; + + groups = mkOption { + type = with types; attrsOf (listOf str); + example = litteralExample '' + { + root = [ "alice" ]; + users = [ "alice" "bob" ]; + } + ''; + description = "Groups of users"; + }; + }; + }; + + config = lib.mkIf cfg.enable { + assertions = [ ] + ++ (lib.flip builtins.map cfg.virtualHosts ({ subdomain, ... } @ args: + let + conflicts = [ "port" "root" ]; + optionsNotNull = builtins.map (v: args.${v} != null) conflicts; + optionsSet = lib.filter lib.id optionsNotNull; + in + { + assertion = builtins.length optionsSet == 1; + message = '' + Subdomain '${subdomain}' must have exactly one of ${ + lib.concatStringsSep ", " (builtins.map (v: "'${v}'") conflicts) + } configured. + ''; + })) + ++ ( + let + ports = lib.my.mapFilter + (v: v != null) + ({ port, ... }: port) + cfg.virtualHosts; + portCounts = lib.my.countValues ports; + nonUniquesCounts = lib.filterAttrs (_: v: v != 1) portCounts; + nonUniques = builtins.attrNames nonUniquesCounts; + mkAssertion = port: { + assertion = false; + message = "Port ${port} cannot appear in multiple virtual hosts."; + }; + in + map mkAssertion nonUniques + ) ++ ( + let + subs = map ({ subdomain, ... }: subdomain) cfg.virtualHosts; + subsCounts = lib.my.countValues subs; + nonUniquesCounts = lib.filterAttrs (_: v: v != 1) subsCounts; + nonUniques = builtins.attrNames nonUniquesCounts; + mkAssertion = v: { + assertion = false; + message = '' + Subdomain '${v}' cannot appear in multiple virtual hosts. + ''; + }; + in + map mkAssertion nonUniques + ) + ; + + services.nginx = { + enable = true; + statusPage = true; # For monitoring scraping. + + recommendedGzipSettings = true; + recommendedOptimisation = true; + recommendedTlsSettings = true; + recommendedProxySettings = true; + + virtualHosts = + let + domain = config.networking.domain; + mkVHost = ({ subdomain, ... } @ args: lib.nameValuePair + "${subdomain}.${domain}" + (lib.my.recursiveMerge [ + # Base configuration + { + forceSSL = true; + useACMEHost = domain; + } + # Proxy to port + (lib.optionalAttrs (args.port != null) { + locations."/".proxyPass = + "http://127.0.0.1:${toString args.port}"; + }) + # Serve filesystem content + (lib.optionalAttrs (args.root != null) { + inherit (args) root; + }) + # VHost specific configuration + args.extraConfig + # SSO configuration + (lib.optionalAttrs args.sso.enable { + extraConfig = (args.extraConfig.extraConfig or "") + '' + error_page 401 = @error401; + ''; + + locations."@error401".return = '' + 302 https://${cfg.sso.subdomain}.${config.networking.domain}/login?go=$scheme://$http_host$request_uri + ''; + + locations."/" = { + extraConfig = + (args.extraConfig.locations."/".extraConfig or "") + '' + # Use SSO + auth_request /sso-auth; + + # Set username through header + auth_request_set $username $upstream_http_x_username; + proxy_set_header X-User $username; + + # Renew SSO cookie on request + auth_request_set $cookie $upstream_http_set_cookie; + add_header Set-Cookie $cookie; + ''; + }; + + locations."/sso-auth" = { + proxyPass = "http://localhost:${toString cfg.sso.port}/auth"; + extraConfig = '' + # Do not allow requests from outside + internal; + + # Do not forward the request body + proxy_pass_request_body off; + proxy_set_header Content-Length ""; + + # Set X-Application according to subdomain for matching + proxy_set_header X-Application "${subdomain}"; + + # Set origin URI for matching + proxy_set_header X-Origin-URI $request_uri; + ''; + }; + }) + ]) + ); + in + lib.my.genAttrs' cfg.virtualHosts mkVHost; + + sso = { + enable = true; + + configuration = { + listen = { + addr = "127.0.0.1"; + inherit (cfg.sso) port; + }; + + audit_log = { + target = [ + "fd://stdout" + ]; + events = [ + "access_denied" + "login_success" + "login_failure" + "logout" + "validate" + ]; + headers = [ + "x-origin-uri" + "x-application" + ]; + }; + + cookie = { + domain = ".${config.networking.domain}"; + secure = true; + authentication_key = { + _secret = cfg.sso.authKeyFile; + }; + }; + + login = { + title = "Ambroisie's SSO"; + default_method = "simple"; + hide_mfa_field = false; + names = { + simple = "Username / Password"; + }; + }; + + providers = { + simple = + let + applyUsers = lib.flip lib.mapAttrs cfg.sso.users; + in + { + users = applyUsers (_: v: { _secret = v.passwordHashFile; }); + + mfa = applyUsers (_: v: [{ + provider = "totp"; + attributes = { + secret = { + _secret = v.totpSecretFile; + }; + }; + }]); + + inherit (cfg.sso) groups; + }; + }; + + acl = { + rule_sets = [ + { + rules = [{ field = "x-application"; present = true; }]; + allow = [ "@root" ]; + } + ]; + }; + }; + }; + }; + + my.services.nginx.virtualHosts = [ + { + subdomain = "login"; + inherit (cfg.sso) port; + } + ]; + + networking.firewall.allowedTCPPorts = [ 80 443 ]; + + # Nginx needs to be able to read the certificates + users.users.nginx.extraGroups = [ "acme" ]; + + security.acme = { + defaults.email = lib.my.mkMailAddress "bruno.acme" "belanyi.fr"; + + acceptTerms = true; + # Use DNS wildcard certificate + certs = + let + domain = config.networking.domain; + in + with pkgs; + { + "${domain}" = { + extraDomainNames = [ "*.${domain}" ]; + dnsProvider = "gandiv5"; + inherit (cfg.acme) credentialsFile; + }; + }; + }; + + services.grafana.provision.dashboards = lib.mkIf cfg.monitoring.enable [ + { + name = "NGINX"; + options.path = pkgs.nur.repos.alarsyo.grafanaDashboards.nginx; + disableDeletion = true; + } + ]; + + services.prometheus = lib.mkIf cfg.monitoring.enable { + exporters.nginx = { + enable = true; + listenAddress = "127.0.0.1"; + }; + + scrapeConfigs = [ + { + job_name = "nginx"; + static_configs = [ + { + targets = [ "127.0.0.1:${toString config.services.prometheus.exporters.nginx.port}" ]; + labels = { + instance = config.networking.hostName; + }; + } + ]; + } + ]; + }; + }; +} diff --git a/modules/services/nginx/sso/default.nix b/modules/services/nginx/sso/default.nix new file mode 100644 index 0000000..13292ec --- /dev/null +++ b/modules/services/nginx/sso/default.nix @@ -0,0 +1,90 @@ +# I must override the module to allow having runtime secrets +{ config, lib, pkgs, utils, ... }: +let + cfg = config.services.nginx.sso; + pkg = lib.getBin cfg.package; + confPath = "/var/lib/nginx-sso/config.json"; +in +{ + disabledModules = [ "services/security/nginx-sso.nix" ]; + + + options.services.nginx.sso = with lib; { + enable = mkEnableOption "nginx-sso service"; + + package = mkOption { + type = types.package; + default = pkgs.nginx-sso; + defaultText = "pkgs.nginx-sso"; + description = '' + The nginx-sso package that should be used. + ''; + }; + + configuration = mkOption { + type = types.attrsOf types.unspecified; + default = { }; + example = literalExample '' + { + listen = { addr = "127.0.0.1"; port = 8080; }; + + providers.token.tokens = { + myuser = "MyToken"; + }; + + acl = { + rule_sets = [ + { + rules = [ { field = "x-application"; equals = "MyApp"; } ]; + allow = [ "myuser" ]; + } + ]; + }; + } + ''; + description = '' + nginx-sso configuration + (documentation) + as a Nix attribute set. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services.nginx-sso = { + description = "Nginx SSO Backend"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + StateDirectory = "nginx-sso"; + WorkingDirectory = "/var/lib/nginx-sso"; + # The files to be merged might not have the correct permissions + ExecStartPre = ''+${pkgs.writeScript "merge-nginx-sso-config" '' + #!${pkgs.bash}/bin/bash + rm -f '${confPath}' + ${utils.genJqSecretsReplacementSnippet cfg.configuration confPath} + + # Fix permissions + chown nginx-sso:nginx-sso ${confPath} + chmod 0600 ${confPath} + '' + }''; + ExecStart = lib.mkForce '' + ${pkg}/bin/nginx-sso \ + --config ${confPath} \ + --frontend-dir ${pkg}/share/frontend + ''; + Restart = "always"; + User = "nginx-sso"; + Group = "nginx-sso"; + }; + }; + + users.users.nginx-sso = { + isSystemUser = true; + group = "nginx-sso"; + }; + + users.groups.nginx-sso = { }; + }; +} diff --git a/modules/services/paperless/default.nix b/modules/services/paperless/default.nix new file mode 100644 index 0000000..5ba9709 --- /dev/null +++ b/modules/services/paperless/default.nix @@ -0,0 +1,152 @@ +{ config, lib, ... }: +let + cfg = config.my.services.paperless; +in +{ + options.my.services.paperless = with lib; { + enable = mkEnableOption "Paperless service"; + + port = mkOption { + type = types.port; + default = 4535; + example = 8080; + description = "Internal port for webui"; + }; + + secretKeyFile = mkOption { + type = types.str; + example = "/var/lib/paperless/secret-key.env"; + description = '' + Secret key as an 'EnvironmentFile' (see `systemd.exec(5)`) + ''; + }; + + documentPath = mkOption { + type = with types; nullOr str; + default = null; + example = "/mnt/paperless"; + description = '' + Path to the directory to store the documents. Use default if null + ''; + }; + + username = mkOption { + type = types.str; + default = "ambroisie"; + example = "username"; + description = "Name of the administrator"; + }; + + passwordFile = mkOption { + type = types.str; + example = "/var/lib/paperless/password.txt"; + description = "Read the administrator's password from this path"; + }; + }; + + config = lib.mkIf cfg.enable { + services.paperless = { + enable = true; + + port = cfg.port; + + mediaDir = lib.mkIf (cfg.documentPath != null) cfg.documentPath; + + extraConfig = + let + paperlessDomain = "paperless.${config.networking.domain}"; + in + { + # Use SSO + PAPERLESS_ENABLE_HTTP_REMOTE_USER = true; + PAPERLESS_HTTP_REMOTE_USER_HEADER_NAME = "HTTP_X_USER"; + + # Use PostgreSQL + PAPERLESS_DBHOST = "/run/postgresql"; + PAPERLESS_DBUSER = "paperless"; + PAPERLESS_DBNAME = "paperless"; + + # Security settings + PAPERLESS_ALLOWED_HOSTS = paperlessDomain; + PAPERLESS_CORS_ALLOWED_HOSTS = "https://${paperlessDomain}"; + + # OCR settings + PAPERLESS_OCR_LANGUAGE = "fra+eng"; + + # Misc + PAPERLESS_TIME_ZONE = config.time.timeZone; + PAPERLESS_ADMIN_USER = cfg.username; + }; + + # Admin password + passwordFile = cfg.passwordFile; + }; + + systemd.services = { + paperless-scheduler = { + after = [ "postgresql.service" ]; + + serviceConfig = { + EnvironmentFile = cfg.secretKeyFile; + }; + }; + + paperless-consumer = { + after = [ "postgresql.service" ]; + + serviceConfig = { + EnvironmentFile = cfg.secretKeyFile; + }; + }; + + paperless-web = { + after = [ "postgresql.service" ]; + + serviceConfig = { + EnvironmentFile = cfg.secretKeyFile; + }; + }; + }; + + # Set-up database + services.postgresql = { + enable = true; + ensureDatabases = [ "paperless" ]; + ensureUsers = [ + { + name = "paperless"; + ensurePermissions."DATABASE paperless" = "ALL PRIVILEGES"; + } + ]; + }; + + # Set-up media group + users.groups.media = { }; + + users.users.${config.services.paperless.user} = { + extraGroups = [ "media" ]; + }; + + my.services.nginx.virtualHosts = [ + { + subdomain = "paperless"; + inherit (cfg) port; + sso = { + enable = true; + }; + + # Enable websockets on root + extraConfig = { + locations."/".proxyWebsockets = true; + }; + } + ]; + + my.services.backup = { + paths = [ + config.services.paperless.dataDir + config.services.paperless.mediaDir + ]; + }; + }; +} diff --git a/services/pirate.nix b/modules/services/pirate/default.nix similarity index 58% rename from services/pirate.nix rename to modules/services/pirate/default.nix index 2eb490b..42dd12b 100644 --- a/services/pirate.nix +++ b/modules/services/pirate/default.nix @@ -5,13 +5,12 @@ { config, lib, ... }: let cfg = config.my.services.pirate; - domain = config.networking.domain; ports = { - sonarr = 8989; - radarr = 7878; bazarr = 6767; lidarr = 8686; + radarr = 7878; + sonarr = 8989; }; managers = with lib.attrsets; @@ -22,15 +21,8 @@ let }) ports); - redirections = with lib.attrsets; - (mapAttrs' - (service: port: nameValuePair "${service}.${domain}" { - forceSSL = true; - useACMEHost = domain; - - locations."/".proxyPass = "http://127.0.0.1:${builtins.toString port}/"; - }) - ports); + redirections = lib.flip lib.mapAttrsToList ports + (subdomain: port: { inherit subdomain port; }); in { options.my.services.pirate = { @@ -38,6 +30,9 @@ in }; config = lib.mkIf cfg.enable { - services = managers // { nginx.virtualHosts = redirections; }; + services = managers; + my.services.nginx.virtualHosts = redirections; + # Set-up media group + users.groups.media = { }; }; } diff --git a/modules/services/podgrab/default.nix b/modules/services/podgrab/default.nix new file mode 100644 index 0000000..9793d60 --- /dev/null +++ b/modules/services/podgrab/default.nix @@ -0,0 +1,41 @@ +# A simple podcast fetcher +{ config, lib, ... }: +let + cfg = config.my.services.podgrab; +in +{ + options.my.services.podgrab = with lib; { + enable = mkEnableOption "Podgrab, a self-hosted podcast manager"; + + passwordFile = mkOption { + type = with types; nullOr str; + default = null; + example = "/run/secrets/password.env"; + description = '' + The path to a file containing the PASSWORD environment variable + definition for Podgrab's authentification. + ''; + }; + + port = mkOption { + type = types.port; + default = 8080; + example = 4242; + description = "The port on which Podgrab will listen for incoming HTTP traffic."; + }; + }; + + config = lib.mkIf cfg.enable { + services.podgrab = { + enable = true; + inherit (cfg) passwordFile port; + }; + + my.services.nginx.virtualHosts = [ + { + subdomain = "podgrab"; + inherit (cfg) port; + } + ]; + }; +} diff --git a/services/postgresql-backup.nix b/modules/services/postgresql-backup/default.nix similarity index 100% rename from services/postgresql-backup.nix rename to modules/services/postgresql-backup/default.nix diff --git a/modules/services/postgresql/default.nix b/modules/services/postgresql/default.nix new file mode 100644 index 0000000..6f51f3e --- /dev/null +++ b/modules/services/postgresql/default.nix @@ -0,0 +1,61 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.services.postgresql; +in +{ + options.my.services.postgresql = with lib; { + enable = my.mkDisableOption "postgres configuration"; + + # Transient option to be enabled for migrations + upgradeScript = mkEnableOption "postgres upgrade script"; + }; + + config = lib.mkMerge [ + # Let other services enable postgres when they need it + (lib.mkIf cfg.enable { + services.postgresql = { + package = pkgs.postgresql_13; + }; + }) + + # Taken from the manual + (lib.mkIf cfg.upgradeScript { + containers.temp-pg.config.services.postgresql = { + enable = true; + package = pkgs.postgresql_13; + }; + + environment.systemPackages = + let + newpg = config.containers.temp-pg.config.services.postgresql; + in + [ + (pkgs.writeScriptBin "upgrade-pg-cluster" '' + #!/usr/bin/env bash + + set -x + export OLDDATA="${config.services.postgresql.dataDir}" + export NEWDATA="${newpg.dataDir}" + export OLDBIN="${config.services.postgresql.package}/bin" + export NEWBIN="${newpg.package}/bin" + + if [ "$OLDDATA" -ef "$NEWDATA" ]; then + echo "Cannot migrate to same data directory" >&2 + exit 1 + fi + + install -d -m 0700 -o postgres -g postgres "$NEWDATA" + cd "$NEWDATA" + sudo -u postgres $NEWBIN/initdb -D "$NEWDATA" + + systemctl stop postgresql # old one + + sudo -u postgres $NEWBIN/pg_upgrade \ + --old-datadir "$OLDDATA" --new-datadir "$NEWDATA" \ + --old-bindir $OLDBIN --new-bindir $NEWBIN \ + "$@" + '') + ]; + }) + ]; +} diff --git a/services/quassel.nix b/modules/services/quassel/default.nix similarity index 100% rename from services/quassel.nix rename to modules/services/quassel/default.nix diff --git a/services/rss-bridge.nix b/modules/services/rss-bridge/default.nix similarity index 60% rename from services/rss-bridge.nix rename to modules/services/rss-bridge/default.nix index ad5141b..85e37c2 100644 --- a/services/rss-bridge.nix +++ b/modules/services/rss-bridge/default.nix @@ -2,8 +2,6 @@ { config, lib, ... }: let cfg = config.my.services.rss-bridge; - domain = config.networking.domain; - rss-bridgeDomain = "rss-bridge.${config.networking.domain}"; in { options.my.services.rss-bridge = { @@ -14,12 +12,13 @@ in services.rss-bridge = { enable = true; whitelist = [ "*" ]; # Whitelist all - virtualHost = rss-bridgeDomain; # Setup virtual host + virtualHost = "rss-bridge.${config.networking.domain}"; }; - services.nginx.virtualHosts."${rss-bridgeDomain}" = { + # The service above configures the domain, no need for my wrapper + services.nginx.virtualHosts."rss-bridge.${config.networking.domain}" = { forceSSL = true; - useACMEHost = domain; + useACMEHost = config.networking.domain; }; }; } diff --git a/services/sabnzbd.nix b/modules/services/sabnzbd/default.nix similarity index 58% rename from services/sabnzbd.nix rename to modules/services/sabnzbd/default.nix index ebeef8b..b9b99cf 100644 --- a/services/sabnzbd.nix +++ b/modules/services/sabnzbd/default.nix @@ -2,9 +2,6 @@ { config, lib, ... }: let cfg = config.my.services.sabnzbd; - - domain = config.networking.domain; - sabnzbdDomain = "sabnzbd.${domain}"; port = 9090; # NOTE: not declaratively set... in { @@ -18,11 +15,14 @@ in group = "media"; }; - services.nginx.virtualHosts."${sabnzbdDomain}" = { - forceSSL = true; - useACMEHost = domain; + # Set-up media group + users.groups.media = { }; - locations."/".proxyPass = "http://127.0.0.1:${toString port}"; - }; + my.services.nginx.virtualHosts = [ + { + subdomain = "sabnzbd"; + inherit port; + } + ]; }; } diff --git a/services/ssh-server.nix b/modules/services/ssh-server/default.nix similarity index 100% rename from services/ssh-server.nix rename to modules/services/ssh-server/default.nix diff --git a/modules/services/tlp/default.nix b/modules/services/tlp/default.nix new file mode 100644 index 0000000..8c9edd6 --- /dev/null +++ b/modules/services/tlp/default.nix @@ -0,0 +1,26 @@ +# TLP power management +{ config, lib, ... }: +let + cfg = config.my.services.tlp; +in +{ + options.my.services.tlp = { + enable = lib.mkEnableOption "TLP power management configuration"; + }; + + config = lib.mkIf cfg.enable { + services.tlp = { + enable = true; + + settings = { + # Set CPU scaling aggressively when power is not an issue + CPU_SCALING_GOVERNOR_ON_AC = "performance"; + CPU_SCALING_GOVERNOR_ON_BAT = "powersave"; + + # Keep charge between 60% and 80% to preserve battery life + START_CHARGE_THRESH_BAT0 = 60; + STOP_CHARGE_THRESH_BAT0 = 80; + }; + }; + }; +} diff --git a/services/transmission.nix b/modules/services/transmission/default.nix similarity index 68% rename from services/transmission.nix rename to modules/services/transmission/default.nix index 29e181b..cac075f 100644 --- a/services/transmission.nix +++ b/modules/services/transmission/default.nix @@ -6,25 +6,18 @@ { config, lib, ... }: let cfg = config.my.services.transmission; - - domain = config.networking.domain; - webuiDomain = "transmission.${domain}"; in { options.my.services.transmission = with lib; { enable = mkEnableOption "Transmission torrent client"; - username = mkOption { + credentialsFile = mkOption { type = types.str; - default = "Ambroisie"; - example = "username"; - description = "Name of the transmission RPC user"; - }; - - password = mkOption { - type = types.str; - example = "password"; - description = "Password of the transmission RPC user"; + example = "/var/lib/transmission/creds.json"; + description = '' + Credential file as an json configuration file to be merged with + the main one. + ''; }; downloadBase = mkOption { @@ -34,7 +27,7 @@ in description = "Download base directory"; }; - privatePort = mkOption { + port = mkOption { type = types.port; default = 9091; example = 8080; @@ -56,6 +49,8 @@ in downloadDirPermissions = "775"; + inherit (cfg) credentialsFile; + settings = { download-dir = "${cfg.downloadBase}/complete"; incomplete-dir = "${cfg.downloadBase}/incomplete"; @@ -63,26 +58,26 @@ in peer-port = cfg.peerPort; rpc-enabled = true; - rpc-port = cfg.privatePort; + rpc-port = cfg.port; rpc-authentication-required = true; - rpc-username = cfg.username; - rpc-password = cfg.password; # Insecure, but I don't care. - # Proxied behind Nginx. rpc-whitelist-enabled = true; rpc-whitelist = "127.0.0.1"; }; }; + # Set-up media group + users.groups.media = { }; + # Default transmission webui, I prefer combustion but its development # seems to have stalled - services.nginx.virtualHosts."${webuiDomain}" = { - forceSSL = true; - useACMEHost = domain; - - locations."/".proxyPass = "http://127.0.0.1:${toString cfg.privatePort}"; - }; + my.services.nginx.virtualHosts = [ + { + subdomain = "transmission"; + inherit (cfg) port; + } + ]; networking.firewall = { allowedTCPPorts = [ cfg.peerPort ]; diff --git a/services/wireguard.nix b/modules/services/wireguard/default.nix similarity index 88% rename from services/wireguard.nix rename to modules/services/wireguard/default.nix index fc948f6..656fdb2 100644 --- a/services/wireguard.nix +++ b/modules/services/wireguard/default.nix @@ -5,9 +5,43 @@ { config, lib, pkgs, ... }: let cfg = config.my.services.wireguard; + secrets = config.age.secrets; hostName = config.networking.hostName; - peers = config.my.secrets.wireguard.peers; + peers = + let + mkPeer = name: attrs: { + inherit (attrs) clientNum publicKey; + privateKeyFile = secrets."wireguard/${name}/private-key".path; + } // lib.optionalAttrs (attrs ? externalIp) { + inherit (attrs) externalIp; + }; + in + lib.mapAttrs mkPeer { + # "Server" + porthos = { + clientNum = 1; + publicKey = "PLdgsizztddri0LYtjuNHr5r2E8D+yI+gM8cm5WDfHQ="; + externalIp = "91.121.177.163"; + }; + + # "Clients" + aramis = { + clientNum = 2; + publicKey = "QJSWIBS1mXTpxYybLlKu/Y5wy0GFbUfn4yPzpF1DZDc="; + }; + + richelieu = { + clientNum = 3; + publicKey = "w4IADAj2Tt7Qe95a0RxDv9ovg/Dr/f3q1LrVOPF48Rk="; + }; + + # Sarah's iPhone + milady = { + clientNum = 4; + publicKey = "3MKEu4F6o8kww54xeAao5Uet86fv8z/QsZ2L2mOzqDQ="; + }; + }; thisPeer = peers."${hostName}"; thisPeerIsServer = thisPeer ? externalIp; # Only connect to clients from server, and only connect to server from clients @@ -18,7 +52,7 @@ let in lib.filterAttrs shouldConnectToPeer allOthers; - extIface = config.my.networking.externalInterface; + extIface = config.my.hardware.networking.externalInterface; mkInterface = clientAllowedIPs: { listenPort = cfg.port; @@ -26,8 +60,7 @@ let "${v4.subnet}.${toString thisPeer.clientNum}/${toString v4.mask}" "${v6.subnet}::${toString thisPeer.clientNum}/${toHexString v6.mask}" ]; - # Insecure, I don't care - privateKey = thisPeer.privateKey; + inherit (thisPeer) privateKeyFile; peers = let diff --git a/modules/system/boot/default.nix b/modules/system/boot/default.nix new file mode 100644 index 0000000..0fed267 --- /dev/null +++ b/modules/system/boot/default.nix @@ -0,0 +1,21 @@ +{ config, lib, ... }: +let + cfg = config.my.system.boot; +in +{ + options.my.system.boot = with lib; { + tmp = { + clean = mkEnableOption "clean `/tmp` on boot."; + + tmpfs = my.mkDisableOption "mount `/tmp` as a tmpfs on boot."; + }; + }; + + config = { + boot = { + cleanTmpDir = cfg.tmp.clean; + + tmpOnTmpfs = cfg.tmp.tmpfs; + }; + }; +} diff --git a/modules/system/default.nix b/modules/system/default.nix new file mode 100644 index 0000000..3c81cac --- /dev/null +++ b/modules/system/default.nix @@ -0,0 +1,14 @@ +# System-related modules +{ ... }: + +{ + imports = [ + ./boot + ./documentation + ./language + ./nix + ./packages + ./podman + ./users + ]; +} diff --git a/modules/system/documentation/default.nix b/modules/system/documentation/default.nix new file mode 100644 index 0000000..74886ad --- /dev/null +++ b/modules/system/documentation/default.nix @@ -0,0 +1,43 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.system.documentation; +in +{ + options.my.system.documentation = with lib.my; { + enable = mkDisableOption "Documentation integration"; + + dev.enable = mkDisableOption "Documentation aimed at developers"; + + info.enable = mkDisableOption "Documentation aimed at developers"; + + man = { + enable = mkDisableOption "Documentation aimed at developers"; + + linux = mkDisableOption "Linux man pages (section 2 & 3)"; + }; + + nixos.enable = mkDisableOption "NixOS documentation"; + }; + + config = lib.mkIf cfg.enable { + documentation = { + enable = true; + + dev.enable = cfg.dev.enable; + + info.enable = cfg.info.enable; + + man = { + enable = cfg.man.enable; + generateCaches = true; + }; + + nixos.enable = cfg.nixos.enable; + }; + + environment.systemPackages = with pkgs; lib.optionals cfg.man.linux [ + man-pages + man-pages-posix + ]; + }; +} diff --git a/modules/system/language/default.nix b/modules/system/language/default.nix new file mode 100644 index 0000000..f2bbcde --- /dev/null +++ b/modules/system/language/default.nix @@ -0,0 +1,22 @@ +# Language settings +{ config, lib, ... }: +let + cfg = config.my.system.language; +in +{ + options.my.system.language = with lib; { + enable = my.mkDisableOption "language configuration"; + + locale = mkOption { + type = types.str; + default = "en_US.UTF-8"; + example = "fr_FR.UTF-8"; + description = "Which locale to use for the system"; + }; + }; + + config = lib.mkIf cfg.enable { + # Select internationalisation properties. + i18n.defaultLocale = cfg.locale; + }; +} diff --git a/modules/system/nix/default.nix b/modules/system/nix/default.nix new file mode 100644 index 0000000..16db0b4 --- /dev/null +++ b/modules/system/nix/default.nix @@ -0,0 +1,45 @@ +# Nix related settings +{ config, inputs, lib, options, pkgs, ... }: +let + cfg = config.my.system.nix; +in +{ + options.my.system.nix = with lib; { + enable = my.mkDisableOption "nix configuration"; + + addToRegistry = my.mkDisableOption "add inputs and self to registry"; + + addToNixPath = my.mkDisableOption "add inputs and self to nix path"; + }; + + config = lib.mkIf cfg.enable (lib.mkMerge [ + { + nix = { + package = pkgs.nixFlakes; + + settings = { + experimental-features = [ "nix-command" "flakes" ]; + }; + }; + } + + (lib.mkIf cfg.addToRegistry { + nix.registry = { + # Allow me to use my custom package using `nix run self#pkg` + self.flake = inputs.self; + # Use pinned nixpkgs when using `nix run pkgs#` + pkgs.flake = inputs.nixpkgs; + # Add NUR to run some packages that are only present there + nur.flake = inputs.nur; + }; + }) + + (lib.mkIf cfg.addToNixPath { + nix.nixPath = options.nix.nixPath.default ++ [ + "self=${inputs.self}" + "pkgs=${inputs.nixpkgs}" + "nur=${inputs.nur}" + ]; + }) + ]); +} diff --git a/modules/system/packages/default.nix b/modules/system/packages/default.nix new file mode 100644 index 0000000..ceb85dd --- /dev/null +++ b/modules/system/packages/default.nix @@ -0,0 +1,33 @@ +# Common packages +{ config, lib, pkgs, ... }: +let + cfg = config.my.system.packages; +in +{ + options.my.system.packages = with lib; { + enable = my.mkDisableOption "packages configuration"; + + allowUnfree = my.mkDisableOption "allow unfree packages"; + }; + + config = lib.mkIf cfg.enable { + environment.systemPackages = with pkgs; [ + vim + wget + ]; + + programs = { + vim.defaultEditor = true; # Modal editing is life + + zsh = { + enable = true; # Use integrations + # Disable global compinit when a user config exists + enableGlobalCompInit = !config.my.home.zsh.enable; + }; + }; + + nixpkgs.config = { + allowUnfree = cfg.allowUnfree; # Because I don't care *that* much. + }; + }; +} diff --git a/modules/system/podman/default.nix b/modules/system/podman/default.nix new file mode 100644 index 0000000..c267ec6 --- /dev/null +++ b/modules/system/podman/default.nix @@ -0,0 +1,25 @@ +# Podman related settings +{ config, inputs, lib, options, pkgs, ... }: +let + cfg = config.my.system.podman; +in +{ + options.my.system.podman = with lib; { + enable = mkEnableOption "podman configuration"; + }; + + config = lib.mkIf cfg.enable { + virtualisation.podman = { + enable = true; + + # Use fake `docker` command to redirect to `podman` + dockerCompat = true; + + # Expose a docker-like socket + dockerSocket.enable = true; + + # Allow DNS resolution in the default network + defaultNetwork.dnsname.enable = true; + }; + }; +} diff --git a/modules/system/users/default.nix b/modules/system/users/default.nix new file mode 100644 index 0000000..3fa5b2e --- /dev/null +++ b/modules/system/users/default.nix @@ -0,0 +1,50 @@ +# User setup +{ config, lib, pkgs, ... }: +let + secrets = config.age.secrets; + cfg = config.my.system.users; + groupExists = grp: builtins.hasAttr grp config.users.groups; + groupsIfExist = builtins.filter groupExists; +in +{ + options.my.system.users = with lib; { + enable = my.mkDisableOption "user configuration"; + }; + + config = lib.mkIf cfg.enable { + users = { + mutableUsers = false; # I want it to be declarative. + + users = { + root = { + passwordFile = secrets."users/root/hashed-password".path; + }; + + ${config.my.user.name} = { + passwordFile = secrets."users/ambroisie/hashed-password".path; + description = "Bruno BELANYI"; + isNormalUser = true; + shell = pkgs.zsh; + extraGroups = groupsIfExist [ + "audio" # sound control + "media" # access to media files + "networkmanager" # wireless configuration + "plugdev" # usage of ZSA keyboard tools + "podman" # usage of `podman` socket + "video" # screen control + "wheel" # `sudo` for the user. + ]; + openssh.authorizedKeys.keys = with builtins; + let + keyDir = ./ssh; + contents = readDir keyDir; + names = attrNames contents; + files = filter (name: contents.${name} == "regular") names; + keys = map (basename: readFile (keyDir + "/${basename}")) files; + in + keys; + }; + }; + }; + }; +} diff --git a/modules/ssh/aramis.pub b/modules/system/users/ssh/aramis.pub similarity index 100% rename from modules/ssh/aramis.pub rename to modules/system/users/ssh/aramis.pub diff --git a/modules/ssh/shared.pub b/modules/system/users/ssh/shared.pub similarity index 100% rename from modules/ssh/shared.pub rename to modules/system/users/ssh/shared.pub diff --git a/modules/users.nix b/modules/users.nix deleted file mode 100644 index 1ace265..0000000 --- a/modules/users.nix +++ /dev/null @@ -1,35 +0,0 @@ -# User setup -{ config, lib, pkgs, ... }: -let - my = config.my; - groupIfExists = grp: - lib.lists.optional - (builtins.hasAttr grp config.users.groups) - grp; - groupsIfExist = builtins.concatMap groupIfExists; -in -{ - users.mutableUsers = false; # I want it to be declarative. - - # Define user accounts and passwords. - users.users.root.hashedPassword = my.secrets.users.root.hashedPassword; - users.users.ambroisie = { - hashedPassword = my.secrets.users.ambroisie.hashedPassword; - description = "Bruno BELANYI"; - isNormalUser = true; - shell = pkgs.zsh; - extraGroups = groupsIfExist [ - "media" # access to media files - "plugdev" # usage of ZSA keyboard tools - "wheel" # `sudo` for the user. - ]; - openssh.authorizedKeys.keys = with builtins; let - keyDir = ./ssh; - contents = readDir keyDir; - names = attrNames contents; - files = filter (name: contents.${name} == "regular") names; - keys = map (basename: readFile (keyDir + "/${basename}")) files; - in - keys; - }; -} diff --git a/overlays/default.nix b/overlays/default.nix new file mode 100644 index 0000000..7984ac0 --- /dev/null +++ b/overlays/default.nix @@ -0,0 +1,3 @@ +{ + # NOTE: no overlays at the moment +} diff --git a/pkgs/bw-pass/bw-pass b/pkgs/bw-pass/bw-pass new file mode 100755 index 0000000..124714a --- /dev/null +++ b/pkgs/bw-pass/bw-pass @@ -0,0 +1,75 @@ +#!/usr/bin/env bash + +AUTO_LOCK=900 # 15min timeout by default + +usage() { + printf '%s\n' "Usage: bw-pass [directory name] " >&2 +} + +prompt_pass() { + rofi -dmenu -password -no-fixed-num-lines -p "$1" +} + +error_out() { + printf '%s\n' "$1" >&2 + rofi -dmenu -no-fixed-num-lines -p "$1" + exit 1 +} + +login() { + local PASSWORD + PASSWORD="$(prompt_pass "Bitwarden Password")" || error_out "Cannot prompt password" + export BW_SESSION + BW_SESSION="$(bw unlock "$PASSWORD" --raw)" || error_out "Cannot unlock" +} + +ensure_logged_in() { + # Use the same keyring as bitwarden-rofi for this + + local KEY_ID + keyctl link @u @s + if ! KEY_ID="$(keyctl request user bw_session 2>/dev/null)"; then + login + KEY_ID="$(keyctl add user bw_session "$BW_SESSION" @u)" + fi + + if [ "$AUTO_LOCK" -gt 0 ]; then + keyctl timeout "$KEY_ID" "$AUTO_LOCK" + fi + export BW_SESSION + BW_SESSION="$(keyctl pipe "$KEY_ID")" + keyctl unlink @u @s +} + +query_password() { + # Either use with `query_password + # Or `query_password ` when the account has no directory + + local FOLDER_ID + local PASSWORD + + if [ $# -eq 2 ]; then + FOLDER_ID="$(bw list folders | + jq '.[] | select(.name == "'"$1"'") | .id' | + cut -d'"' -f2)" + shift + else + FOLDER_ID=null + fi + PASSWORD="$(bw list items --folderid "$FOLDER_ID" | + jq '.[] | select(.name == "'"$1"'") | .login.password' | + cut -d'"' -f2)" + + if [ -z "$PASSWORD" ]; then + error_out "Did not find password for '$1'" + fi + printf '%s\n' "$PASSWORD" +} + +if [ $# -lt 1 ] || [ $# -gt 2 ]; then + usage + exit 1 +fi + +ensure_logged_in +query_password "$@" diff --git a/pkgs/bw-pass/default.nix b/pkgs/bw-pass/default.nix new file mode 100644 index 0000000..fcd9d08 --- /dev/null +++ b/pkgs/bw-pass/default.nix @@ -0,0 +1,42 @@ +{ lib, bitwarden-cli, coreutils, jq, keyutils, makeWrapper, rofi, stdenvNoCC }: +stdenvNoCC.mkDerivation rec { + pname = "bw-pass"; + version = "0.1.0"; + + src = ./bw-pass; + + buildInputs = [ + makeWrapper + ]; + + dontUnpack = true; + + dontBuild = true; + + installPhase = '' + mkdir -p $out/bin + cp $src $out/bin/${pname} + chmod a+x $out/bin/${pname} + ''; + + wrapperPath = lib.makeBinPath [ + bitwarden-cli + coreutils + jq + keyutils + rofi + ]; + + fixupPhase = '' + patchShebangs $out/bin/${pname} + wrapProgram $out/bin/${pname} --prefix PATH : "${wrapperPath}" + ''; + + meta = with lib; { + description = "A simple script to query a password from bitwarden"; + homepage = "https://gitea.belanyi.fr/ambroisie/nix-config"; + license = with licenses; [ mit ]; + platforms = platforms.linux; + maintainers = with maintainers; [ ambroisie ]; + }; +} diff --git a/pkgs/change-audio/change-audio b/pkgs/change-audio/change-audio new file mode 100755 index 0000000..612fecf --- /dev/null +++ b/pkgs/change-audio/change-audio @@ -0,0 +1,75 @@ +#!/usr/bin/env bash + +set -euo pipefail + +NOTIFY=( + notify-send + -u low + -h string:x-canonical-private-synchronous:change-audio +) + +do_change_volume() { + local args=() + + if [ "$1" = "up" ]; then + args+=("-i") + else + args+=("-d") + fi + shift + + # Do not boost over 100% unless explitily asked for + if [ "$1" = "--force" ] || [ "$1" = "-f" ]; then + args=("--allow-boost" "${args[@]}") + shift + fi + + # Volume + args+=("$1") + + pamixer "${args[@]}" + + newVolume="$(pamixer --get-volume || true)" + [ "$(pamixer --get-volume-human)" = "muted" ] && isMuted=true + + MSG="Set volume to $newVolume%" + if [ "${isMuted:-false}" = true ]; then + MSG="$MSG (muted)" + fi + "${NOTIFY[@]}" \ + -h "int:value:$newVolume" \ + -- "$MSG" +} + +do_toggle() { + local args=() + if [ "${2:-audio}" = mic ]; then + args+=(--default-source) + MSG="Toggled microphone" + else + MSG="Toggled audio output" + fi + + pamixer "${args[@]}" --toggle-mute + + if [ "$(pamixer "${args[@]}" --get-mute)" = true ]; then + MSG="$MSG (muted)" + else + MSG="$MSG (unmuted)" + fi + + "${NOTIFY[@]}" -- "$MSG" +} + +case "$1" in + up|down) + do_change_volume "$@" + ;; + toggle) + do_toggle "$@" + ;; + *) + echo "No suche option '$1'" >&2 + exit 1 + ;; +esac diff --git a/pkgs/change-audio/default.nix b/pkgs/change-audio/default.nix new file mode 100644 index 0000000..3b3359d --- /dev/null +++ b/pkgs/change-audio/default.nix @@ -0,0 +1,41 @@ +{ lib, libnotify, makeWrapper, pamixer, stdenvNoCC }: +stdenvNoCC.mkDerivation rec { + pname = "change-audio"; + version = "0.3.0"; + + src = ./change-audio; + + buildInputs = [ + makeWrapper + ]; + + dontUnpack = true; + + dontBuild = true; + + installPhase = '' + mkdir -p $out/bin + cp $src $out/bin/change-audio + chmod a+x $out/bin/change-audio + ''; + + wrapperPath = lib.makeBinPath [ + libnotify + pamixer + ]; + + fixupPhase = '' + patchShebangs $out/bin/change-audio + wrapProgram $out/bin/change-audio --prefix PATH : "${wrapperPath}" + ''; + + meta = with lib; { + description = '' + A script to change the volume and notify about it + ''; + homepage = "https://gitea.belanyi.fr/ambroisie/nix-config"; + license = with licenses; [ mit ]; + platforms = platforms.linux; + maintainers = with maintainers; [ ambroisie ]; + }; +} diff --git a/pkgs/change-backlight/change-backlight b/pkgs/change-backlight/change-backlight new file mode 100755 index 0000000..e178151 --- /dev/null +++ b/pkgs/change-backlight/change-backlight @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +set -euo pipefail + +if [ "$1" = "up" ]; then + upDown="+$2%" +else + upDown="$2%-" +fi + +newBrightness="$(brightnessctl -m set "$upDown" | cut -d, -f4)" +notify-send -u low \ + -h string:x-canonical-private-synchronous:change-backlight \ + -h "int:value:${newBrightness/\%/}" \ + -- "Set brightness to $newBrightness" diff --git a/pkgs/change-backlight/default.nix b/pkgs/change-backlight/default.nix new file mode 100644 index 0000000..83ba2fe --- /dev/null +++ b/pkgs/change-backlight/default.nix @@ -0,0 +1,41 @@ +{ lib, brightnessctl, libnotify, makeWrapper, stdenvNoCC }: +stdenvNoCC.mkDerivation rec { + pname = "change-backlight"; + version = "0.1.0"; + + src = ./change-backlight; + + buildInputs = [ + makeWrapper + ]; + + dontUnpack = true; + + dontBuild = true; + + installPhase = '' + mkdir -p $out/bin + cp $src $out/bin/change-backlight + chmod a+x $out/bin/change-backlight + ''; + + wrapperPath = lib.makeBinPath [ + brightnessctl + libnotify + ]; + + fixupPhase = '' + patchShebangs $out/bin/change-backlight + wrapProgram $out/bin/change-backlight --prefix PATH : "${wrapperPath}" + ''; + + meta = with lib; { + description = '' + A script to change a screen's brightness and notify about it + ''; + homepage = "https://gitea.belanyi.fr/ambroisie/nix-config"; + license = with licenses; [ mit ]; + platforms = platforms.linux; + maintainers = with maintainers; [ ambroisie ]; + }; +} diff --git a/pkgs/comma/comma b/pkgs/comma/comma new file mode 100755 index 0000000..ba5c6ae --- /dev/null +++ b/pkgs/comma/comma @@ -0,0 +1,33 @@ +#!/usr/bin/env bash + +set -euo pipefail + +print_err() { + printf "%s\n" "$1" >&2 +} + +usage() { + print_err "Usage: , [argument]..." +} + +find_program() { + local CANDIDATE + CANDIDATE="$(nix-locate --top-level --minimal --at-root --whole-name "/bin/$1")" + if [ "$(printf '%s\n' "$CANDIDATE" | wc -l)" -gt 1 ]; then + CANDIDATE="$(printf '%s' "$CANDIDATE" | fzf-tmux)" + fi + printf '%s' "$CANDIDATE" +} + +if [ $# -lt 1 ]; then + usage + exit 1 +fi + +PROGRAM="$(find_program "$1")" +if [ -z "$PROGRAM" ]; then + print_err "No match found for $1" + exit 1 +fi + +nix shell "${COMMA_PKGS_FLAKE:-nixpkgs}#$PROGRAM" -c "$@" diff --git a/pkgs/comma/default.nix b/pkgs/comma/default.nix new file mode 100644 index 0000000..d61c884 --- /dev/null +++ b/pkgs/comma/default.nix @@ -0,0 +1,40 @@ +{ lib, fzf, makeWrapper, nix-index, stdenvNoCC }: +stdenvNoCC.mkDerivation rec { + pname = "comma"; + version = "0.1.0"; + + src = ./comma; + + buildInputs = [ + makeWrapper + ]; + + dontUnpack = true; + + dontBuild = true; + + installPhase = '' + mkdir -p $out/bin + cp $src $out/bin/${meta.mainProgram} + chmod a+x $out/bin/${meta.mainProgram} + ''; + + wrapperPath = lib.makeBinPath [ + fzf + nix-index + ]; + + fixupPhase = '' + patchShebangs $out/bin/${meta.mainProgram} + wrapProgram $out/bin/${meta.mainProgram} --prefix PATH : "${wrapperPath}" + ''; + + meta = with lib; { + mainProgram = ","; + description = "A simple script inspired by Shopify's comma, for modern Nix"; + homepage = "https://gitea.belanyi.fr/ambroisie/nix-config"; + license = with licenses; [ mit ]; + platforms = platforms.unix; + maintainers = with maintainers; [ ambroisie ]; + }; +} diff --git a/pkgs/default.nix b/pkgs/default.nix index 58f004a..5d4e3d8 100644 --- a/pkgs/default.nix +++ b/pkgs/default.nix @@ -1,18 +1,42 @@ { pkgs }: -rec { +pkgs.lib.makeScope pkgs.newScope (pkgs: { + bw-pass = pkgs.callPackage ./bw-pass { }; + + change-audio = pkgs.callPackage ./change-audio { }; + + change-backlight = pkgs.callPackage ./change-backlight { }; + + comma = pkgs.callPackage ./comma { }; + diff-flake = pkgs.callPackage ./diff-flake { }; + dragger = pkgs.callPackage ./dragger { }; + + drone-rsync = pkgs.callPackage ./drone-rsync { }; + + drone-scp = pkgs.callPackage ./drone-scp { }; + + ff2mpv-go = pkgs.callPackage ./ff2mpv-go { }; + havm = pkgs.callPackage ./havm { }; + i3-get-window-criteria = pkgs.callPackage ./i3-get-window-criteria { }; + lohr = pkgs.callPackage ./lohr { }; + matrix-notifier = pkgs.callPackage ./matrix-notifier { }; + nolimips = pkgs.callPackage ./nolimips { }; - podgrab = pkgs.callPackage ./podgrab { }; + psst = pkgs.callPackage ./psst { }; - unbound-zones-adblock = pkgs.callPackage ./unbound-zones-adblock { - inherit unified-hosts-lists; - }; + rofi-bluetooth = pkgs.callPackage ./rofi-bluetooth { }; + + unbound-zones-adblock = pkgs.callPackage ./unbound-zones-adblock { }; unified-hosts-lists = pkgs.callPackage ./unified-hosts-lists { }; -} + + vimix-cursors = pkgs.callPackage ./vimix-cursors { }; + + volantes-cursors = pkgs.callPackage ./volantes-cursors { }; +}) diff --git a/pkgs/diff-flake/default.nix b/pkgs/diff-flake/default.nix index 9511952..39e8921 100644 --- a/pkgs/diff-flake/default.nix +++ b/pkgs/diff-flake/default.nix @@ -1,20 +1,17 @@ -{ coreutils, git, gnused, makeWrapper, lib, shellcheck, stdenvNoCC }: +{ lib, coreutils, git, gnused, makeWrapper, stdenvNoCC }: stdenvNoCC.mkDerivation rec { pname = "diff-flake"; - version = "0.1.0"; + version = "0.2.0"; src = ./diff-flake; - phases = [ "buildPhase" "installPhase" "fixupPhase" ]; - buildInputs = [ makeWrapper - shellcheck ]; - buildPhase = '' - shellcheck $src - ''; + dontUnpack = true; + + dontBuild = true; installPhase = '' mkdir -p $out/bin @@ -38,5 +35,6 @@ stdenvNoCC.mkDerivation rec { homepage = "https://gitea.belanyi.fr/ambroisie/nix-config"; license = with licenses; [ mit ]; platforms = platforms.unix; + maintainers = with maintainers; [ ambroisie ]; }; } diff --git a/pkgs/diff-flake/diff-flake b/pkgs/diff-flake/diff-flake index ef03122..7c106c1 100755 --- a/pkgs/diff-flake/diff-flake +++ b/pkgs/diff-flake/diff-flake @@ -28,7 +28,7 @@ add_shell() { SYSTEM="$(nix eval --raw --impure --expr 'builtins.currentSystem')" fi # Use 'inputDerivation' attribute to make sure that it is build-able - FLAKE_OUTPUTS+=("devShell.$SYSTEM.inputDerivation") + FLAKE_OUTPUTS+=("devShells.$SYSTEM.default.inputDerivation") } add_host() { diff --git a/pkgs/dragger/default.nix b/pkgs/dragger/default.nix new file mode 100644 index 0000000..e535944 --- /dev/null +++ b/pkgs/dragger/default.nix @@ -0,0 +1,29 @@ +{ lib, fetchFromGitHub, qt5, }: +qt5.mkDerivation rec { + pname = "dragger"; + version = "0.1.0"; + + src = fetchFromGitHub { + owner = "ambroisie"; + repo = "dragger"; + rev = "v${version}"; + sha256 = "sha256-WAC720DxfkQxy1BeeGzE6IerFb4ejoMRAPEJv5HGDHM="; + }; + + configurePhase = '' + qmake + ''; + + installPhase = '' + mkdir -p $out/bin + cp dragger $out/bin + ''; + + meta = with lib; { + description = "A CLI drag-and-drop tool"; + homepage = "https://gitea.belanyi.fr/ambroisie/dragger"; + license = licenses.mit; + maintainers = with maintainers; [ ambroisie ]; + platforms = platforms.linux; + }; +} diff --git a/pkgs/drone-rsync/default.nix b/pkgs/drone-rsync/default.nix new file mode 100644 index 0000000..cb70fed --- /dev/null +++ b/pkgs/drone-rsync/default.nix @@ -0,0 +1,40 @@ +{ lib, makeWrapper, openssh, rsync, sshpass, stdenvNoCC }: +stdenvNoCC.mkDerivation rec { + pname = "drone-rsync"; + version = "0.1.0"; + + src = ./drone-rsync; + + buildInputs = [ + makeWrapper + ]; + + dontUnpack = true; + + dontBuild = true; + + installPhase = '' + mkdir -p $out/bin + cp $src $out/bin/${pname} + chmod a+x $out/bin/${pname} + ''; + + wrapperPath = lib.makeBinPath [ + openssh + rsync + sshpass + ]; + + fixupPhase = '' + patchShebangs $out/bin/${pname} + wrapProgram $out/bin/${pname} --prefix PATH : "${wrapperPath}" + ''; + + meta = with lib; { + description = "Helper script to run rsync in a CI pipeline"; + homepage = "https://gitea.belanyi.fr/ambroisie/nix-config"; + license = with licenses; [ mit ]; + platforms = platforms.unix; + maintainers = with maintainers; [ ambroisie ]; + }; +} diff --git a/pkgs/drone-rsync/drone-rsync b/pkgs/drone-rsync/drone-rsync new file mode 100755 index 0000000..b6491e7 --- /dev/null +++ b/pkgs/drone-rsync/drone-rsync @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +set -euo pipefail + +ARGS=( + # Show readable progress in log + --verbose + --human-readable + --progress + # Have a one-to-one copy + --archive + --compress + --recursive + --delete + # Configure ssh client + --rsh "ssh -p ${SYNC_PORT:-22} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" +) + +eval "$(ssh-agent)" +SSHPASS="${SYNC_PASSPHRASE:-}" sshpass -P 'passphrase' -v -e ssh-add <(echo "${SYNC_KEY}") + +if [ -n "${SYNC_DRY_RUN:-}" ]; then + ARGS+=(--dry-run) +fi + +# shellcheck disable=2086 +# FIXME: have a safer way to allow globbing the source +rsync \ + "${ARGS[@]}" \ + ${SYNC_SOURCE} \ + "${SYNC_USERNAME}@${SYNC_HOST}:${SYNC_TARGET}" diff --git a/pkgs/drone-scp/default.nix b/pkgs/drone-scp/default.nix new file mode 100644 index 0000000..0b51032 --- /dev/null +++ b/pkgs/drone-scp/default.nix @@ -0,0 +1,24 @@ +{ lib, buildGoModule, fetchFromGitHub }: +buildGoModule rec { + pname = "drone-scp"; + version = "1.6.3"; + + src = fetchFromGitHub { + owner = "appleboy"; + repo = "drone-scp"; + rev = "v${version}"; + sha256 = "sha256-ELjPqoRR4O6gmc/PgthQuSXuSTQNzBZoAUT80zVVbV0="; + }; + + vendorSha256 = "sha256-/c103hTJ/Qdz2KTkdl/ACvAaSSTKcl1DQY3+Us6OxaI="; + + doCheck = false; # Needs a specific user... + + meta = with lib; { + description = '' + Copy files and artifacts via SSH using a binary, docker or Drone CI + ''; + homepage = "https://github.com/appleboy/drone-scp"; + license = licenses.mit; + }; +} diff --git a/pkgs/ff2mpv-go/default.nix b/pkgs/ff2mpv-go/default.nix new file mode 100644 index 0000000..3dc229c --- /dev/null +++ b/pkgs/ff2mpv-go/default.nix @@ -0,0 +1,30 @@ +{ lib, buildGoModule, fetchgit, mpv }: +buildGoModule rec { + pname = "ff2mpv-go"; + version = "1.0.1"; + + src = fetchgit { + url = "https://git.clsr.net/util/ff2mpv-go/"; + rev = "v${version}"; + sha256 = "sha256-e/AuOA3isFTyBf97Zwtr16yo49UdYzvktV5PKB/eH/s="; + }; + + vendorSha256 = "sha256-pQpattmS9VmO3ZIQUFn66az8GSmB4IvYhTTCFn6SUmo="; + + postPatch = '' + sed -i -e 's,"mpv","${mpv}/bin/mpv",' ff2mpv.go + ''; + + postInstall = '' + mkdir -p "$out/lib/mozilla/native-messaging-hosts" + $out/bin/ff2mpv-go --manifest > "$out/lib/mozilla/native-messaging-hosts/ff2mpv.json" + ''; + + meta = with lib; { + description = '' + Native messaging host for ff2mpv written in Go. + ''; + homepage = "https://git.clsr.net/util/ff2mpv-go/"; + license = licenses.publicDomain; + }; +} diff --git a/pkgs/havm/default.nix b/pkgs/havm/default.nix index 20817fc..51eb9f0 100644 --- a/pkgs/havm/default.nix +++ b/pkgs/havm/default.nix @@ -1,4 +1,4 @@ -{ fetchurl, ghc, lib, stdenv, which }: +{ lib, fetchurl, ghc, stdenv, which }: stdenv.mkDerivation rec { pname = "havm"; version = "0.28"; @@ -8,9 +8,12 @@ stdenv.mkDerivation rec { sha256 = "sha256-FDi4FZ8rjGqRkFlROtcJsv+mks7MmIXQGV4bZrwkQrA="; }; - buildInputs = [ + nativeBuildInputs = [ ghc - which # Used by tests + ]; + + checkInputs = [ + which ]; doCheck = true; @@ -26,5 +29,6 @@ stdenv.mkDerivation rec { homepage = "https://www.lrde.epita.fr/wiki/Havm"; license = licenses.gpl2Plus; platforms = platforms.all; + maintainers = with maintainers; [ ambroisie ]; }; } diff --git a/pkgs/i3-get-window-criteria/default.nix b/pkgs/i3-get-window-criteria/default.nix new file mode 100644 index 0000000..8b082d4 --- /dev/null +++ b/pkgs/i3-get-window-criteria/default.nix @@ -0,0 +1,41 @@ +{ lib, coreutils, gnused, makeWrapper, stdenvNoCC, xorg }: +stdenvNoCC.mkDerivation rec { + pname = "i3-get-window-criteria"; + version = "0.1.0"; + + src = ./i3-get-window-criteria; + + buildInputs = [ + makeWrapper + ]; + + dontUnpack = true; + + dontBuild = true; + + installPhase = '' + mkdir -p $out/bin + cp $src $out/bin/${pname} + chmod a+x $out/bin/${pname} + ''; + + wrapperPath = lib.makeBinPath [ + coreutils + gnused + xorg.xprop + xorg.xwininfo + ]; + + fixupPhase = '' + patchShebangs $out/bin/${pname} + wrapProgram $out/bin/${pname} --prefix PATH : "${wrapperPath}" + ''; + + meta = with lib; { + description = "Helper script to query i3 window criterions"; + homepage = "https://gitea.belanyi.fr/ambroisie/nix-config"; + license = with licenses; [ mit ]; + platforms = platforms.unix; + maintainers = with maintainers; [ ambroisie ]; + }; +} diff --git a/pkgs/i3-get-window-criteria/i3-get-window-criteria b/pkgs/i3-get-window-criteria/i3-get-window-criteria new file mode 100755 index 0000000..dba386e --- /dev/null +++ b/pkgs/i3-get-window-criteria/i3-get-window-criteria @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +# i3-get-window-criteria - Get criteria for use with i3 config commands + +# To use, run this script, then click on a window. +# Output is in the format: [= = ...] + +# Known problem: when WM_NAME is used as fallback for the 'title=""' criterion, +# quotes in "" are not escaped properly. This is a problem with the output of `xprop`, +# reported upstream: https://bugs.freedesktop.org/show_bug.cgi?id=66807 + +match_int='[0-9][0-9]*' +match_string='".*"' +match_qstring='"[^"\\]*(\\.[^"\\]*)*"' # NOTE: Adds 1 backreference + +{ + # Run xwininfo, get window id + window_id=$(xwininfo -int | sed -nre "s/^xwininfo: Window id: ($match_int) .*$/\1/p") + echo "id=$window_id" + + # Run xprop, transform its output into i3 criteria. Handle fallback to + # WM_NAME when _NET_WM_NAME isn't set + xprop -id "$window_id" | + sed -nr \ + -e "s/^WM_CLASS\(STRING\) = ($match_qstring), ($match_qstring)$/instance=\1\nclass=\3/p" \ + -e "s/^WM_WINDOW_ROLE\(STRING\) = ($match_qstring)$/window_role=\1/p" \ + -e "/^WM_NAME\(STRING\) = ($match_string)$/ {s//title=\1/; h}" \ + -e "/^_NET_WM_NAME\(UTF8_STRING\) = ($match_qstring)$/ {s//title=\1/; h}" \ + -e '$ {g; p}' +} | sort | tr "\n" " " | sed -r 's/^ *(.*) $/[\1]\n/' diff --git a/pkgs/lohr/default.nix b/pkgs/lohr/default.nix index 1ceb018..c71dbd0 100644 --- a/pkgs/lohr/default.nix +++ b/pkgs/lohr/default.nix @@ -1,4 +1,4 @@ -{ fetchFromGitHub, lib, rustPlatform }: +{ lib, fetchFromGitHub, rustPlatform }: rustPlatform.buildRustPackage rec { pname = "lohr"; version = "0.4.0"; @@ -17,5 +17,6 @@ rustPlatform.buildRustPackage rec { homepage = "https://github.com/alarsyo/lohr"; license = with licenses; [ mit asl20 ]; platforms = platforms.unix; + maintainers = with maintainers; [ ambroisie ]; }; } diff --git a/pkgs/matrix-notifier/default.nix b/pkgs/matrix-notifier/default.nix new file mode 100644 index 0000000..76765a7 --- /dev/null +++ b/pkgs/matrix-notifier/default.nix @@ -0,0 +1,44 @@ +{ lib, curl, jq, fetchFromGitHub, makeWrapper, pandoc, stdenvNoCC }: +stdenvNoCC.mkDerivation rec { + pname = "matrix-notifier"; + version = "0.2.0"; + + src = fetchFromGitHub { + owner = "ambroisie"; + repo = "matrix-notifier"; + rev = "v${version}"; + sha256 = "sha256-JiKPDrr9wyD2q5Vsac+OkFdvrDkx6mj/oC7XDVnka74="; + }; + + nativeBuildInputs = [ + makeWrapper + ]; + + installPhase = '' + mkdir -p $out/bin + cp $src/${pname} $out/bin/${pname} + chmod a+x $out/bin/${pname} + ''; + + wrapperPath = lib.makeBinPath [ + curl + jq + pandoc + ]; + + fixupPhase = '' + patchShebangs $out/bin/${pname} + wrapProgram $out/bin/${pname} --prefix PATH : "${wrapperPath}" + ''; + + meta = with lib; { + description = '' + A very simple bash script that can be used to send a message to + a Matrix room + ''; + homepage = "https://gitea.belanyi.fr/ambroisie/${pname}"; + license = licenses.mit; + platforms = platforms.unix; + maintainers = with maintainers; [ ambroisie ]; + }; +} diff --git a/pkgs/nolimips/default.nix b/pkgs/nolimips/default.nix index ff5c9b0..65d847d 100644 --- a/pkgs/nolimips/default.nix +++ b/pkgs/nolimips/default.nix @@ -1,4 +1,4 @@ -{ fetchurl, gnulib, lib, stdenv }: +{ lib, fetchurl, stdenv }: stdenv.mkDerivation rec { pname = "nolimips"; version = "0.11"; @@ -19,5 +19,6 @@ stdenv.mkDerivation rec { homepage = "https://www.lrde.epita.fr/wiki/Nolimips"; license = licenses.gpl2; platforms = platforms.all; + maintainers = with maintainers; [ ambroisie ]; }; } diff --git a/pkgs/podgrab/default.nix b/pkgs/podgrab/default.nix deleted file mode 100644 index 7f18416..0000000 --- a/pkgs/podgrab/default.nix +++ /dev/null @@ -1,29 +0,0 @@ -{ buildGoModule, fetchFromGitHub, lib }: - -buildGoModule rec { - pname = "podgrab"; - version = "2021-03-26"; - - src = fetchFromGitHub { - owner = "akhilrex"; - repo = "podgrab"; - rev = "3179a875b8b638fb86d0e829d12a9761c1cd7f90"; - sha256 = "sha256-vhxIm20ZUi+RusrAsSY54tv/D570/oMO5qLz9dNqgqo="; - }; - - vendorSha256 = "sha256-xY9xNuJhkWPgtqA/FBVIp7GuWOv+3nrz6l3vaZVLlIE="; - - postInstall = '' - mkdir -p $out/share/ - cp -r "$src/client" "$out/share/" - cp -r "$src/webassets" "$out/share/" - ''; - - meta = with lib; { - description = '' - A self-hosted podcast manager to download episodes as soon as they become live - ''; - homepage = "https://github.com/akhilrex/podgrab"; - license = licenses.gpl3; - }; -} diff --git a/pkgs/psst/default.nix b/pkgs/psst/default.nix new file mode 100644 index 0000000..6eca44b --- /dev/null +++ b/pkgs/psst/default.nix @@ -0,0 +1,34 @@ +{ lib, alsa-lib, cairo, dbus, fetchFromGitHub, gtk3, openssl, pkg-config, rustPlatform }: +rustPlatform.buildRustPackage rec { + pname = "psst"; + version = "unstable-2022-01-13"; + + src = fetchFromGitHub { + owner = "jpochyla"; + repo = "psst"; + rev = "8f142a3232a706537c8477bff43d2e52309f6b78"; + sha256 = "sha256-YA9p6KHuZXt43OrfShO5d3Cj8L8GPpczRQlncJqM7QI="; + }; + + nativeBuildInputs = [ + pkg-config + ]; + + buildInputs = [ + alsa-lib + cairo + dbus + gtk3 + openssl + ]; + + cargoSha256 = "sha256-iA/ja7B73JyiXQ9kBzk1C5wtX+HPBrngCS+8rFDHbcs="; + + meta = with lib; { + description = "Fast and multi-platform Spotify client with native GUI"; + homepage = "https://github.com/jpochyla/psst"; + platforms = platforms.linux; + license = with licenses; [ mit ]; + maintainers = with maintainers; [ ambroisie ]; + }; +} diff --git a/pkgs/rofi-bluetooth/default.nix b/pkgs/rofi-bluetooth/default.nix new file mode 100644 index 0000000..2ff40a1 --- /dev/null +++ b/pkgs/rofi-bluetooth/default.nix @@ -0,0 +1,40 @@ +{ lib, bluez, fetchFromGitHub, makeWrapper, rofi, stdenvNoCC }: +stdenvNoCC.mkDerivation rec { + pname = "rofi-bluetooth"; + version = "unstable-2021-10-15"; + + src = fetchFromGitHub { + owner = "nickclyde"; + repo = "rofi-bluetooth"; + rev = "893db1f2b549e7bc0e9c62e7670314349a29cdf2"; + sha256 = "sha256-3oROJKEQCuSnLfbJ+JSSc9hcmJTPrLHRQJsrUcaOMss="; + }; + + buildInputs = [ + makeWrapper + ]; + + installPhase = '' + mkdir -p $out/bin + cp $src/rofi-bluetooth $out/bin/ + chmod a+x $out/bin/rofi-bluetooth + ''; + + wrapperPath = lib.makeBinPath [ + rofi + bluez + ]; + + fixupPhase = '' + patchShebangs $out/bin/${pname} + wrapProgram $out/bin/${pname} --prefix PATH : "${wrapperPath}" + ''; + + meta = with lib; { + description = "A rofi menu for managing bluetooth connections"; + homepage = "https://github.com/nickclyde/rofi-bluetooth/commit/"; + license = with licenses; [ gpl3Only ]; + platforms = platforms.linux; + maintainers = with maintainers; [ ambroisie ]; + }; +} diff --git a/pkgs/unbound-zones-adblock/default.nix b/pkgs/unbound-zones-adblock/default.nix index c4309bd..ecec917 100644 --- a/pkgs/unbound-zones-adblock/default.nix +++ b/pkgs/unbound-zones-adblock/default.nix @@ -1,11 +1,11 @@ -{ fetchFromGitHub, gawk, lib, stdenvNoCC, unified-hosts-lists }: +{ lib, gawk, stdenvNoCC, unified-hosts-lists }: stdenvNoCC.mkDerivation rec { name = "unbound-zones-adblock"; version = unified-hosts-lists.version; src = unified-hosts-lists; - phases = [ "installPhase" ]; + dontUnpack = true; installPhase = let @@ -33,5 +33,6 @@ stdenvNoCC.mkDerivation rec { homepage = "https://github.com/StevenBlack/hosts"; license = licenses.mit; platforms = platforms.all; + maintainers = with maintainers; [ ambroisie ]; }; } diff --git a/pkgs/unified-hosts-lists/default.nix b/pkgs/unified-hosts-lists/default.nix index af55994..61ac0c4 100644 --- a/pkgs/unified-hosts-lists/default.nix +++ b/pkgs/unified-hosts-lists/default.nix @@ -1,16 +1,16 @@ -{ fetchFromGitHub, lib, stdenvNoCC }: +{ lib, fetchFromGitHub, stdenvNoCC }: stdenvNoCC.mkDerivation rec { pname = "unified-hosts-lists"; - version = "3.6.4"; + version = "3.10.1"; src = fetchFromGitHub { owner = "StevenBlack"; repo = "hosts"; rev = version; - sha256 = "sha256-U6vRwbFSYka2VS8M1z0n+FaTkKKwdV/cCWIKxp487/I="; + sha256 = "sha256-PFKKYtssjAJGrP3AQE32ZJGlxwxnFa0vUTpCn94fCFI="; }; - phases = [ "installPhase" ]; + dontUnpack = true; installPhase = '' mkdir -p $out @@ -29,5 +29,6 @@ stdenvNoCC.mkDerivation rec { homepage = "https://github.com/StevenBlack/hosts"; license = licenses.mit; platforms = platforms.all; + maintainers = with maintainers; [ ambroisie ]; }; } diff --git a/pkgs/vimix-cursors/default.nix b/pkgs/vimix-cursors/default.nix new file mode 100644 index 0000000..1bbbe4e --- /dev/null +++ b/pkgs/vimix-cursors/default.nix @@ -0,0 +1,44 @@ +{ lib, python3, fetchFromGitHub, inkscape, stdenvNoCC, xcursorgen }: +let + py = python3.withPackages (ps: with ps; [ cairosvg ]); +in +stdenvNoCC.mkDerivation rec { + pname = "vimix-cursors"; + version = "unstable-2020-04-28"; + + src = fetchFromGitHub { + owner = "vinceliuice"; + repo = pname; + rev = "27ebb1935944bc986bf8ae85ee3343b8351d9823"; + sha256 = "sha256-bIPRrKaNQ2Eo+T6zv7qeA1z7uRHXezM0yxh+uqA01Gs="; + }; + + nativeBuildInputs = [ + inkscape + py + xcursorgen + ]; + + postPatch = '' + patchShebangs . + ''; + + buildPhase = '' + HOME="$NIX_BUILD_ROOT" ./build.sh + ''; + + installPhase = '' + install -dm 755 $out/share/icons + for color in "" "-white"; do + cp -pr dist''${color}/ "$out/share/icons/Vimix''${color}-cursors" + done + ''; + + meta = with lib; { + description = "An X cursor theme inspired by Materia design"; + homepage = "https://github.com/vinceliuice/Vimix-cursors"; + license = licenses.gpl3Only; + platforms = platforms.linux; + maintainers = with maintainers; [ ambroisie ]; + }; +} diff --git a/pkgs/volantes-cursors/default.nix b/pkgs/volantes-cursors/default.nix new file mode 100644 index 0000000..e300a36 --- /dev/null +++ b/pkgs/volantes-cursors/default.nix @@ -0,0 +1,44 @@ +{ lib, fetchFromGitHub, inkscape, stdenvNoCC, xcursorgen }: +stdenvNoCC.mkDerivation rec { + pname = "volantes-cursors"; + version = "unstable-2020-06-06"; + + src = fetchFromGitHub { + owner = "varlesh"; + repo = pname; + rev = "d1d290ff42cc4fa643716551bd0b02582b90fd2f"; + sha256 = "sha256-irMN/enoo90nYLfvSOScZoYdvhZKvqqp+grZB2BQD9o="; + }; + + nativeBuildInputs = [ + inkscape + xcursorgen + ]; + + postPatch = '' + patchShebangs . + # The script tries to build in its source directory... + substituteInPlace build.sh --replace \ + ': "''${BUILD_DIR:="$SCRIPT_DIR"/build}"' \ + "BUILD_DIR=$(pwd)/build" + substituteInPlace build.sh --replace \ + ': "''${OUT_DIR:="$SCRIPT_DIR"/dist}"' \ + "OUT_DIR=$(pwd)/dist" + ''; + + buildPhase = '' + HOME="$NIX_BUILD_ROOT" ./build.sh + ''; + + installPhase = '' + make install PREFIX= DESTDIR=$out/ + ''; + + meta = with lib; { + description = "Classic cursor with a flying style"; + homepage = "https://github.com/varlesh/volantes-cursors"; + license = licenses.gpl2Only; + platforms = platforms.linux; + maintainers = with maintainers; [ ambroisie ]; + }; +} diff --git a/profiles/bluetooth/default.nix b/profiles/bluetooth/default.nix new file mode 100644 index 0000000..292d0d1 --- /dev/null +++ b/profiles/bluetooth/default.nix @@ -0,0 +1,15 @@ +{ config, lib, ... }: +let + cfg = config.my.profiles.bluetooth; +in +{ + options.my.profiles.bluetooth = with lib; { + enable = mkEnableOption "bluetooth profile"; + }; + + config = lib.mkIf cfg.enable { + my.hardware.bluetooth.enable = true; + + my.home.bluetooth.enable = true; + }; +} diff --git a/profiles/default.nix b/profiles/default.nix new file mode 100644 index 0000000..43d5a84 --- /dev/null +++ b/profiles/default.nix @@ -0,0 +1,12 @@ +# Configuration that spans accross system and home, or are almagations of modules +{ ... }: +{ + imports = [ + ./bluetooth + ./devices + ./gtk + ./laptop + ./wm + ./x + ]; +} diff --git a/profiles/devices/default.nix b/profiles/devices/default.nix new file mode 100644 index 0000000..54088b3 --- /dev/null +++ b/profiles/devices/default.nix @@ -0,0 +1,17 @@ +{ config, lib, ... }: +let + cfg = config.my.profiles.devices; +in +{ + options.my.profiles.devices = with lib; { + enable = mkEnableOption "devices profile"; + }; + + config = lib.mkIf cfg.enable { + my.hardware = { + ergodox.enable = true; + + mx-ergo.enable = true; + }; + }; +} diff --git a/profiles/gtk/default.nix b/profiles/gtk/default.nix new file mode 100644 index 0000000..a8d6d9a --- /dev/null +++ b/profiles/gtk/default.nix @@ -0,0 +1,17 @@ +{ config, lib, ... }: +let + cfg = config.my.profiles.gtk; +in +{ + options.my.profiles.gtk = with lib; { + enable = mkEnableOption "gtk profile"; + }; + + config = lib.mkIf cfg.enable { + # Allow setting GTK configuration using home-manager + programs.dconf.enable = true; + + # GTK theme configuration + my.home.gtk.enable = true; + }; +} diff --git a/profiles/laptop/default.nix b/profiles/laptop/default.nix new file mode 100644 index 0000000..20a29d7 --- /dev/null +++ b/profiles/laptop/default.nix @@ -0,0 +1,23 @@ +{ config, lib, ... }: +let + cfg = config.my.profiles.laptop; +in +{ + options.my.profiles.laptop = with lib; { + enable = mkEnableOption "laptop profile"; + }; + + config = lib.mkIf cfg.enable { + # Enable touchpad support + services.xserver.libinput.enable = true; + + # Enable TLP power management + my.services.tlp.enable = true; + + # Enable upower power management + my.hardware.upower.enable = true; + + # Enable battery notifications + my.home.power-alert.enable = true; + }; +} diff --git a/profiles/wm/default.nix b/profiles/wm/default.nix new file mode 100644 index 0000000..473d49d --- /dev/null +++ b/profiles/wm/default.nix @@ -0,0 +1,27 @@ +{ config, lib, ... }: +let + cfg = config.my.profiles.wm; +in +{ + options.my.profiles.wm = with lib; { + windowManager = mkOption { + type = with types; nullOr (enum [ "i3" ]); + default = null; + example = "i3"; + description = "Which window manager to use"; + }; + }; + + config = lib.mkMerge [ + (lib.mkIf (cfg.windowManager == "i3") { + # Enable i3 + services.xserver.windowManager.i3.enable = true; + # i3 settings + my.home.wm.windowManager = "i3"; + # Screenshot tool + my.home.flameshot.enable = true; + # Auto disk mounter + my.home.udiskie.enable = true; + }) + ]; +} diff --git a/profiles/x/default.nix b/profiles/x/default.nix new file mode 100644 index 0000000..e9d9cfd --- /dev/null +++ b/profiles/x/default.nix @@ -0,0 +1,23 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.profiles.x; +in +{ + options.my.profiles.x = with lib; { + enable = mkEnableOption "X profile"; + }; + + config = lib.mkIf cfg.enable { + # Enable the X11 windowing system. + services.xserver.enable = true; + # Nice wallpaper + services.xserver.displayManager.lightdm.background = + let + wallpapers = "${pkgs.plasma-workspace-wallpapers}/share/wallpapers"; + in + "${wallpapers}/summer_1am/contents/images/2560x1600.jpg"; + + # X configuration + my.home.x.enable = true; + }; +} diff --git a/secrets/.gitattributes b/secrets/.gitattributes deleted file mode 100644 index a741d4d..0000000 --- a/secrets/.gitattributes +++ /dev/null @@ -1,3 +0,0 @@ -* filter=git-crypt diff=git-crypt -.gitattributes !filter !diff -/default.nix !filter !diff diff --git a/secrets/acme/key.env b/secrets/acme/key.env deleted file mode 100644 index 061d6c1..0000000 Binary files a/secrets/acme/key.env and /dev/null differ diff --git a/secrets/backup/credentials.env b/secrets/backup/credentials.env deleted file mode 100644 index 5b75142..0000000 Binary files a/secrets/backup/credentials.env and /dev/null differ diff --git a/secrets/backup/password.txt b/secrets/backup/password.txt deleted file mode 100644 index a8f640c..0000000 Binary files a/secrets/backup/password.txt and /dev/null differ diff --git a/secrets/canary b/secrets/canary deleted file mode 100644 index e910ea3..0000000 Binary files a/secrets/canary and /dev/null differ diff --git a/secrets/default.nix b/secrets/default.nix deleted file mode 100644 index 0028899..0000000 --- a/secrets/default.nix +++ /dev/null @@ -1,52 +0,0 @@ -{ lib, pkgs, ... }: - -with lib; -let - canaryHash = builtins.hashFile "sha256" ./canary; - expectedHash = - "9df8c065663197b5a1095122d48e140d3677d860343256abd5ab6e4fb4c696ab"; -in -if canaryHash != expectedHash then - abort "Secrets are not readable. Have you run `git-crypt unlock`?" -else { - options.my.secrets = mkOption { - type = types.attrs; - }; - - config.my.secrets = { - acme.key = fileContents ./acme/key.env; - - backup = { - password = fileContents ./backup/password.txt; - credentials = readFile ./backup/credentials.env; - }; - - drone = { - gitea = readFile ./drone/gitea.env; - secret = readFile ./drone/secret.env; - ssh = { - publicKey = readFile ./drone/ssh/key.pub; - privateKey = readFile ./drone/ssh/key; - }; - }; - - lohr.secret = fileContents ./lohr/secret.txt; - - matrix.secret = fileContents ./matrix/secret.txt; - - miniflux.password = fileContents ./miniflux/password.txt; - - nextcloud.password = fileContents ./nextcloud/password.txt; - - podgrab.password = fileContents ./podgrab/password.txt; - - transmission.password = fileContents ./transmission/password.txt; - - users = { - ambroisie.hashedPassword = fileContents ./users/ambroisie/password.txt; - root.hashedPassword = fileContents ./users/root/password.txt; - }; - - wireguard = pkgs.callPackage ./wireguard { }; - }; -} diff --git a/secrets/drone/gitea.env b/secrets/drone/gitea.env deleted file mode 100644 index 82b190c..0000000 Binary files a/secrets/drone/gitea.env and /dev/null differ diff --git a/secrets/drone/secret.env b/secrets/drone/secret.env deleted file mode 100644 index 647d161..0000000 Binary files a/secrets/drone/secret.env and /dev/null differ diff --git a/secrets/drone/ssh/key b/secrets/drone/ssh/key deleted file mode 100644 index 1b70a14..0000000 Binary files a/secrets/drone/ssh/key and /dev/null differ diff --git a/secrets/drone/ssh/key.pub b/secrets/drone/ssh/key.pub deleted file mode 100644 index ca1b5e8..0000000 Binary files a/secrets/drone/ssh/key.pub and /dev/null differ diff --git a/secrets/lohr/secret.txt b/secrets/lohr/secret.txt deleted file mode 100644 index cbc3a26..0000000 Binary files a/secrets/lohr/secret.txt and /dev/null differ diff --git a/secrets/matrix/secret.txt b/secrets/matrix/secret.txt deleted file mode 100644 index ce64730..0000000 Binary files a/secrets/matrix/secret.txt and /dev/null differ diff --git a/secrets/miniflux/password.txt b/secrets/miniflux/password.txt deleted file mode 100644 index 482d1b7..0000000 Binary files a/secrets/miniflux/password.txt and /dev/null differ diff --git a/secrets/nextcloud/password.txt b/secrets/nextcloud/password.txt deleted file mode 100644 index c2e458c..0000000 Binary files a/secrets/nextcloud/password.txt and /dev/null differ diff --git a/secrets/podgrab/password.txt b/secrets/podgrab/password.txt deleted file mode 100644 index 81da33c..0000000 Binary files a/secrets/podgrab/password.txt and /dev/null differ diff --git a/secrets/transmission/password.txt b/secrets/transmission/password.txt deleted file mode 100644 index b1b7c2a..0000000 Binary files a/secrets/transmission/password.txt and /dev/null differ diff --git a/secrets/users/ambroisie/password.txt b/secrets/users/ambroisie/password.txt deleted file mode 100644 index 65fbdfb..0000000 Binary files a/secrets/users/ambroisie/password.txt and /dev/null differ diff --git a/secrets/users/root/password.txt b/secrets/users/root/password.txt deleted file mode 100644 index 6fe87e9..0000000 Binary files a/secrets/users/root/password.txt and /dev/null differ diff --git a/secrets/wireguard/.gitattributes b/secrets/wireguard/.gitattributes deleted file mode 100644 index d4bba55..0000000 --- a/secrets/wireguard/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -/default.nix filter diff diff --git a/secrets/wireguard/aramis/public.key b/secrets/wireguard/aramis/public.key deleted file mode 100644 index 892536e..0000000 Binary files a/secrets/wireguard/aramis/public.key and /dev/null differ diff --git a/secrets/wireguard/aramis/secret.key b/secrets/wireguard/aramis/secret.key deleted file mode 100644 index 5f858e4..0000000 Binary files a/secrets/wireguard/aramis/secret.key and /dev/null differ diff --git a/secrets/wireguard/porthos/public.key b/secrets/wireguard/porthos/public.key deleted file mode 100644 index d89e768..0000000 Binary files a/secrets/wireguard/porthos/public.key and /dev/null differ diff --git a/secrets/wireguard/porthos/secret.key b/secrets/wireguard/porthos/secret.key deleted file mode 100644 index 1ecc84b..0000000 Binary files a/secrets/wireguard/porthos/secret.key and /dev/null differ diff --git a/secrets/wireguard/richelieu/public.key b/secrets/wireguard/richelieu/public.key deleted file mode 100644 index 2ad8bbc..0000000 Binary files a/secrets/wireguard/richelieu/public.key and /dev/null differ diff --git a/secrets/wireguard/richelieu/secret.key b/secrets/wireguard/richelieu/secret.key deleted file mode 100644 index 8b351b6..0000000 Binary files a/secrets/wireguard/richelieu/secret.key and /dev/null differ diff --git a/services/blog.nix b/services/blog.nix deleted file mode 100644 index 0a5fcf1..0000000 --- a/services/blog.nix +++ /dev/null @@ -1,39 +0,0 @@ -# My blog setup -{ config, lib, ... }: -let - cfg = config.my.services.blog; - domain = config.networking.domain; - - makeHostInfo = name: { - name = "${name}.${domain}"; - value = "/var/www/${name}"; - }; - - hostsInfo = [ - { - name = domain; - value = "/var/www/blog"; - } - ] ++ builtins.map makeHostInfo [ "cv" "dev" "key" ]; - - hosts = builtins.listToAttrs hostsInfo; - - makeVirtualHost = with lib.attrsets; - name: root: nameValuePair "${name}" { - forceSSL = true; - useACMEHost = domain; - inherit root; - # Make my blog the default landing site - default = (name == domain); - }; -in -{ - options.my.services.blog = { - enable = lib.mkEnableOption "Blog hosting"; - }; - - config = lib.mkIf cfg.enable { - services.nginx.virtualHosts = with lib.attrsets; - mapAttrs' makeVirtualHost hosts; - }; -} diff --git a/services/default.nix b/services/default.nix deleted file mode 100644 index fc377d9..0000000 --- a/services/default.nix +++ /dev/null @@ -1,28 +0,0 @@ -{ ... }: - -{ - imports = [ - ./adblock.nix - ./backup.nix - ./blog.nix - ./calibre-web.nix - ./drone.nix - ./gitea.nix - ./indexers.nix - ./jellyfin.nix - ./lohr.nix - ./matrix.nix - ./miniflux.nix - ./nextcloud.nix - ./nginx.nix - ./pirate.nix - ./podgrab.nix - ./postgresql-backup.nix - ./quassel.nix - ./rss-bridge.nix - ./sabnzbd.nix - ./ssh-server.nix - ./transmission.nix - ./wireguard.nix - ]; -} diff --git a/services/drone.nix b/services/drone.nix deleted file mode 100644 index 50119ac..0000000 --- a/services/drone.nix +++ /dev/null @@ -1,196 +0,0 @@ -# A docker-based CI/CD system -# -# Inspired by [1] -# [1]: https://github.com/Mic92/dotfiles/blob/master/nixos/eve/modules/drone.nix -{ config, lib, pkgs, ... }: -let - cfg = config.my.services.drone; - - domain = config.networking.domain; - droneDomain = "drone.${domain}"; - - hasRunner = (name: builtins.elem name cfg.runners); - - execPkg = pkgs.drone-runner-exec; - - dockerPkg = pkgs.drone-runner-docker; -in -{ - options.my.services.drone = with lib; { - enable = mkEnableOption "Drone CI"; - runners = mkOption { - type = with types; listOf (enum [ "exec" "docker" ]); - default = [ ]; - example = [ "exec" "docker" ]; - description = "Types of runners to enable"; - }; - admin = mkOption { - type = types.str; - default = "ambroisie"; - example = "admin"; - description = "Name of the admin user"; - }; - port = mkOption { - type = types.port; - default = 3030; - example = 8080; - description = "Internal port of the Drone UI"; - }; - secretFile = mkOption { - type = types.str; - example = "/run/secrets/drone-gitea.env"; - description = "Secrets to inject into Drone server"; - }; - sharedSecretFile = mkOption { - type = types.str; - example = "/run/secrets/drone-rpc.env"; - description = "Shared RPC secret to inject into server and runners"; - }; - }; - - config = lib.mkIf cfg.enable { - systemd.services.drone-server = { - wantedBy = [ "multi-user.target" ]; - after = [ "postgresql.service" ]; - serviceConfig = { - EnvironmentFile = [ - cfg.secretFile - cfg.sharedSecretFile - ]; - Environment = [ - "DRONE_DATABASE_DATASOURCE=postgres:///drone?host=/run/postgresql" - "DRONE_SERVER_HOST=${droneDomain}" - "DRONE_SERVER_PROTO=https" - "DRONE_DATABASE_DRIVER=postgres" - "DRONE_SERVER_PORT=:${toString cfg.port}" - "DRONE_USER_CREATE=username:${cfg.admin},admin:true" - "DRONE_JSONNET_ENABLED=true" - "DRONE_STARLARK_ENABLED=true" - ]; - ExecStart = "${pkgs.drone}/bin/drone-server"; - User = "drone"; - Group = "drone"; - }; - }; - - users.users.drone = { - isSystemUser = true; - createHome = true; - group = "drone"; - }; - users.groups.drone = { }; - - services.postgresql = { - ensureDatabases = [ "drone" ]; - ensureUsers = [{ - name = "drone"; - ensurePermissions = { - "DATABASE drone" = "ALL PRIVILEGES"; - }; - }]; - }; - - services.nginx.virtualHosts."${droneDomain}" = { - forceSSL = true; - useACMEHost = domain; - - locations."/".proxyPass = "http://127.0.0.1:${toString cfg.port}"; - }; - - # Docker runner - systemd.services.drone-runner-docker = lib.mkIf (hasRunner "docker") { - wantedBy = [ "multi-user.target" ]; - after = [ "docker.socket" ]; # Needs the socket to be available - # might break deployment - restartIfChanged = false; - confinement.enable = true; - serviceConfig = { - Environment = [ - "DRONE_SERVER_HOST=${droneDomain}" - "DRONE_SERVER_PROTO=https" - "DRONE_RUNNER_CAPACITY=10" - "CLIENT_DRONE_RPC_HOST=127.0.0.1:${toString cfg.port}" - ]; - BindPaths = [ - "/var/run/docker.sock" - ]; - EnvironmentFile = [ - cfg.sharedSecretFile - ]; - ExecStart = "${dockerPkg}/bin/drone-runner-docker"; - User = "drone-runner-docker"; - Group = "drone-runner-docker"; - }; - }; - - # Make sure it is activated in that case - virtualisation.docker.enable = lib.mkIf (hasRunner "docker") true; - - users.users.drone-runner-docker = lib.mkIf (hasRunner "docker") { - isSystemUser = true; - group = "drone-runner-docker"; - extraGroups = [ "docker" ]; # Give access to the daemon - }; - users.groups.drone-runner-docker = lib.mkIf (hasRunner "docker") { }; - - # Exec runner - systemd.services.drone-runner-exec = lib.mkIf (hasRunner "exec") { - wantedBy = [ "multi-user.target" ]; - # might break deployment - restartIfChanged = false; - confinement.enable = true; - confinement.packages = with pkgs; [ - git - gnutar - bash - nixUnstable - gzip - ]; - path = with pkgs; [ - git - gnutar - bash - nixUnstable - gzip - ]; - serviceConfig = { - Environment = [ - "DRONE_SERVER_HOST=${droneDomain}" - "DRONE_SERVER_PROTO=https" - "DRONE_RUNNER_CAPACITY=10" - "CLIENT_DRONE_RPC_HOST=127.0.0.1:${toString cfg.port}" - "NIX_REMOTE=daemon" - "PAGER=cat" - ]; - BindPaths = [ - "/nix/var/nix/daemon-socket/socket" - "/run/nscd/socket" - ]; - BindReadOnlyPaths = [ - "/etc/resolv.conf:/etc/resolv.conf" - "/etc/resolvconf.conf:/etc/resolvconf.conf" - "/etc/passwd:/etc/passwd" - "/etc/group:/etc/group" - "/nix/var/nix/profiles/system/etc/nix:/etc/nix" - "${config.environment.etc."ssl/certs/ca-certificates.crt".source}:/etc/ssl/certs/ca-certificates.crt" - "${config.environment.etc."ssh/ssh_known_hosts".source}:/etc/ssh/ssh_known_hosts" - "/etc/machine-id" - # channels are dynamic paths in the nix store, therefore we need to bind mount the whole thing - "/nix/" - ]; - EnvironmentFile = [ - cfg.sharedSecretFile - ]; - ExecStart = "${execPkg}/bin/drone-runner-exec"; - User = "drone-runner-exec"; - Group = "drone-runner-exec"; - }; - }; - - users.users.drone-runner-exec = lib.mkIf (hasRunner "exec") { - isSystemUser = true; - group = "drone-runner-exec"; - }; - users.groups.drone-runner-exec = lib.mkIf (hasRunner "exec") { }; - }; -} diff --git a/services/gitea.nix b/services/gitea.nix deleted file mode 100644 index ea739d5..0000000 --- a/services/gitea.nix +++ /dev/null @@ -1,77 +0,0 @@ -# A low-ressource, full-featured git forge. -{ config, lib, ... }: -let - cfg = config.my.services.gitea; - domain = config.networking.domain; - giteaDomain = "gitea.${config.networking.domain}"; -in -{ - options.my.services.gitea = with lib; { - enable = mkEnableOption "Gitea"; - port = mkOption { - type = types.port; - default = 3042; - example = 8080; - description = "Internal port"; - }; - }; - - config = lib.mkIf cfg.enable { - services.gitea = { - enable = true; - - appName = "Ambroisie's forge"; - httpPort = cfg.port; - domain = giteaDomain; - rootUrl = "https://${giteaDomain}"; - - user = "git"; - lfs.enable = true; - - useWizard = false; - disableRegistration = true; - - # only send cookies via HTTPS - cookieSecure = true; - - database = { - type = "postgres"; # Automatic setup - user = "git"; # User needs to be the same as gitea user - }; - - # NixOS module uses `gitea dump` to backup repositories and the database, - # but it produces a single .zip file that's not very backup friendly. - # I configure my backup system manually below. - dump.enable = false; - }; - - users.users.git = { - description = "Gitea Service"; - home = config.services.gitea.stateDir; - useDefaultShell = true; - group = "git"; - - # The service for gitea seems to hardcode the group as - # gitea, so, uh, just in case? - extraGroups = [ "gitea" ]; - - isSystemUser = true; - }; - users.groups.git = { }; - - # Proxy to Gitea - services.nginx.virtualHosts."${giteaDomain}" = { - forceSSL = true; - useACMEHost = domain; - - locations."/".proxyPass = "http://127.0.0.1:${toString cfg.port}/"; - }; - - my.services.backup = { - paths = [ - config.services.gitea.lfs.contentDir - config.services.gitea.repositoryRoot - ]; - }; - }; -} diff --git a/services/indexers.nix b/services/indexers.nix deleted file mode 100644 index 07e0f52..0000000 --- a/services/indexers.nix +++ /dev/null @@ -1,44 +0,0 @@ -# Torrent and usenet meta-indexers -{ config, lib, ... }: -let - cfg = config.my.services.indexers; - - domain = config.networking.domain; - jackettDomain = "jackett.${config.networking.domain}"; - nzbhydraDomain = "nzbhydra.${config.networking.domain}"; - - jackettPort = 9117; - nzbhydraPort = 5076; -in -{ - options.my.services.indexers = with lib; { - jackett.enable = mkEnableOption "Jackett torrent meta-indexer"; - nzbhydra.enable = mkEnableOption "NZBHydra2 torrent meta-indexer"; - }; - - config = { - services.jackett = lib.mkIf cfg.jackett.enable { - enable = true; - }; - - services.nginx.virtualHosts."${jackettDomain}" = - lib.mkIf cfg.jackett.enable { - forceSSL = true; - useACMEHost = domain; - - locations."/".proxyPass = "http://127.0.0.1:${toString jackettPort}/"; - }; - - services.nzbhydra2 = lib.mkIf cfg.nzbhydra.enable { - enable = true; - }; - - services.nginx.virtualHosts."${nzbhydraDomain}" = - lib.mkIf cfg.nzbhydra.enable { - forceSSL = true; - useACMEHost = domain; - - locations."/".proxyPass = "http://127.0.0.1:${toString nzbhydraPort}/"; - }; - }; -} diff --git a/services/jellyfin.nix b/services/jellyfin.nix deleted file mode 100644 index dc48354..0000000 --- a/services/jellyfin.nix +++ /dev/null @@ -1,30 +0,0 @@ -# A FLOSS media server -{ config, lib, ... }: -let - cfg = config.my.services.jellyfin; - domain = config.networking.domain; - jellyfinDomain = "jellyfin.${config.networking.domain}"; -in -{ - options.my.services.jellyfin = { - enable = lib.mkEnableOption "Jellyfin Media Server"; - }; - - config = lib.mkIf cfg.enable { - services.jellyfin = { - enable = true; - group = "media"; - }; - - # Proxy to Jellyfin - services.nginx.virtualHosts."${jellyfinDomain}" = { - forceSSL = true; - useACMEHost = domain; - - locations."/" = { - proxyPass = "http://127.0.0.1:8096/"; - proxyWebsockets = true; - }; - }; - }; -} diff --git a/services/miniflux.nix b/services/miniflux.nix deleted file mode 100644 index 035bfaf..0000000 --- a/services/miniflux.nix +++ /dev/null @@ -1,67 +0,0 @@ -# A minimalist, opinionated feed reader -{ config, lib, ... }: -let - cfg = config.my.services.miniflux; - - domain = config.networking.domain; - minifluxDomain = "reader.${config.networking.domain}"; -in -{ - options.my.services.miniflux = with lib; { - enable = mkEnableOption "Miniflux feed reader"; - - username = mkOption { - type = types.str; - default = "Ambroisie"; - example = "username"; - description = "Name of the admin user"; - }; - - password = mkOption { - type = types.str; - example = "password"; - description = "Password of the admin user"; - }; - - privatePort = mkOption { - type = types.port; - default = 9876; - example = 8080; - description = "Internal port for webui"; - }; - }; - - config = lib.mkIf cfg.enable { - # The service automatically sets up the DB - services.miniflux = { - enable = true; - - adminCredentialsFile = - # Insecure, I don't care. - builtins.toFile "credentials.env" '' - ADMIN_USERNAME=${cfg.username} - ADMIN_PASSWORD=${cfg.password} - ''; - - config = { - # Virtual hosts settings - BASE_URL = "https://${minifluxDomain}"; - LISTEN_ADDR = "localhost:${toString cfg.privatePort}"; - # I want fast updates - POLLING_FREQUENCY = "30"; - BATCH_SIZE = "50"; - # I am a hoarder - CLEANUP_ARCHIVE_UNREAD_DAYS = "-1"; - CLEANUP_ARCHIVE_READ_DAYS = "-1"; - }; - }; - - # Proxy to Jellyfin - services.nginx.virtualHosts."${minifluxDomain}" = { - forceSSL = true; - useACMEHost = domain; - - locations."/".proxyPass = "http://127.0.0.1:${toString cfg.privatePort}/"; - }; - }; -} diff --git a/services/nginx.nix b/services/nginx.nix deleted file mode 100644 index ab90760..0000000 --- a/services/nginx.nix +++ /dev/null @@ -1,44 +0,0 @@ -# Configuration shamelessly stolen from [1] -# -# [1]: https://github.com/delroth/infra.delroth.net/blob/master/common/nginx.nix -{ config, pkgs, lib, ... }: - -{ - # Whenever something defines an nginx vhost, ensure that nginx defaults are - # properly set. - config = lib.mkIf ((builtins.attrNames config.services.nginx.virtualHosts) != [ ]) { - services.nginx = { - enable = true; - statusPage = true; # For monitoring scraping. - - recommendedGzipSettings = true; - recommendedOptimisation = true; - recommendedTlsSettings = true; - recommendedProxySettings = true; - }; - - networking.firewall.allowedTCPPorts = [ 80 443 ]; - - # Nginx needs to be able to read the certificates - users.users.nginx.extraGroups = [ "acme" ]; - - # Use DNS wildcard certificate - security.acme = { - email = "bruno.acme@belanyi.fr"; - acceptTerms = true; - certs = - let - domain = config.networking.domain; - key = config.my.secrets.acme.key; - in - with pkgs; - { - "${domain}" = { - extraDomainNames = [ "*.${domain}" ]; - dnsProvider = "gandiv5"; - credentialsFile = writeText "key.env" key; # Unsecure, I don't care. - }; - }; - }; - }; -} diff --git a/services/podgrab.nix b/services/podgrab.nix deleted file mode 100644 index 556ffff..0000000 --- a/services/podgrab.nix +++ /dev/null @@ -1,63 +0,0 @@ -# A simple podcast fetcher. See [1] -# -# [1]: https://github.com/NixOS/nixpkgs/pull/106008 -{ config, lib, pkgs, ... }: -let - cfg = config.my.services.podgrab; - - domain = config.networking.domain; - podgrabDomain = "podgrab.${domain}"; - - podgrabPkg = pkgs.ambroisie.podgrab; -in -{ - options.my.services.podgrab = with lib; { - enable = mkEnableOption "Podgrab, a self-hosted podcast manager"; - - passwordFile = mkOption { - type = with types; nullOr str; - default = null; - example = "/run/secrets/password.env"; - description = '' - The path to a file containing the PASSWORD environment variable - definition for Podgrab's authentification. - ''; - }; - - port = mkOption { - type = types.port; - default = 8080; - example = 4242; - description = "The port on which Podgrab will listen for incoming HTTP traffic."; - }; - }; - - config = lib.mkIf cfg.enable { - systemd.services.podgrab = { - description = "Podgrab podcast manager"; - wantedBy = [ "multi-user.target" ]; - environment = { - CONFIG = "/var/lib/podgrab/config"; - DATA = "/var/lib/podgrab/data"; - GIN_MODE = "release"; - PORT = toString cfg.port; - }; - serviceConfig = { - DynamicUser = true; - EnvironmentFile = lib.optional (cfg.passwordFile != null) [ - cfg.passwordFile - ]; - ExecStart = "${podgrabPkg}/bin/podgrab"; - WorkingDirectory = "${podgrabPkg}/share"; - StateDirectory = [ "podgrab/config" "podgrab/data" ]; - }; - }; - - services.nginx.virtualHosts."${podgrabDomain}" = { - forceSSL = true; - useACMEHost = domain; - - locations."/".proxyPass = "http://127.0.0.1:${toString cfg.port}"; - }; - }; -}