diff --git a/.drone.yml b/.drone.yml deleted file mode 100644 index 7ad1c78..0000000 --- a/.drone.yml +++ /dev/null @@ -1,24 +0,0 @@ -kind: pipeline -name: check config - -steps: - - name: format check - image: nixos/nix - commands: - - nix-shell -p nixpkgs-fmt --run 'nixpkgs-fmt . --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 diff --git a/.envrc b/.envrc index 9556665..95ed6fb 100644 --- a/.envrc +++ b/.envrc @@ -1,8 +1,10 @@ -use_flake() { - watch_file flake.nix - watch_file flake.lock - eval "$(nix print-dev-env)" -} +if ! has nix_direnv_version || ! nix_direnv_version 2.2.1; then + source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.2.1/direnvrc" "sha256-zelF0vLbEl5uaqrfIzbgNzJWGmLzCmYAkInj/LNxvKs=" +fi -ulimit -s unlimited # Bypass current bug in `nix` flakes evaluation use flake + +watch_file ./flake/checks.nix +watch_file ./flake/dev-shells.nix + +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/.stylua.toml b/.stylua.toml new file mode 100644 index 0000000..394e884 --- /dev/null +++ b/.stylua.toml @@ -0,0 +1 @@ +indent_type = "Spaces" diff --git a/.woodpecker/check.yml b/.woodpecker/check.yml new file mode 100644 index 0000000..d7c5dff --- /dev/null +++ b/.woodpecker/check.yml @@ -0,0 +1,26 @@ +labels: + backend: local + +pipeline: +- name: nix flake check + image: bash + commands: + - nix flake check + +- name: notifiy + image: bash + secrets: + - source: matrix_homeserver + target: address + - source: matrix_roomid + target: room + - source: matrix_username + target: user + - source: matrix_password + target: pass + commands: + - nix run '.#matrix-notifier' + when: + status: + - failure + - success 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..b1c418e 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -1,5 +1,5 @@ #!/usr/bin/env nix-shell -#! nix-shell -i bash -p bitwarden-cli git gnupg jq nixFlakes +#! nix-shell -i bash -p bitwarden-cli git gnupg jq nix # Command failure is script failure set -e @@ -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/agenix.pub" 644 + get_doc "SysAdmin/SSH" "agenix-private" "$HOME/.ssh/agenix" 600 } get_pgp() { @@ -78,22 +80,13 @@ get_pgp() { } get_creds() { - BW_SESSION="$(bw login --raw)" + BW_SESSION="$(bw login --raw || bw unlock --raw)" export BW_SESSION get_ssh get_pgp } -setup_gpg() { - info 'Setting up loopback pinentry for GnuPG' - echo "allow-loopback-pinentry" > ~/.gnupg/gpg-agent.conf - - info 'Signing dummy message to ensure GnuPG key is usable by `git-crypt`' - echo whatever | gpg --clearsign --armor --pinentry loopback --output /dev/null -} - [ -z "$NOCREDS" ] && get_creds -[ -z "$NOGPG" ] && setup_gpg nix --experimental-features 'nix-command flakes' develop diff --git a/flake.lock b/flake.lock index f80cf84..b3c3e01 100644 --- a/flake.lock +++ b/flake.lock @@ -1,21 +1,129 @@ { "nodes": { - "futils": { + "agenix": { + "inputs": { + "darwin": "darwin", + "home-manager": [ + "home-manager" + ], + "nixpkgs": [ + "nixpkgs" + ] + }, "locked": { - "lastModified": 1619345332, - "narHash": "sha256-qHnQkEp1uklKTpx3MvKtY6xzgcqXDsz5nLilbbuL+3A=", + "lastModified": 1690228878, + "narHash": "sha256-9Xe7JV0krp4RJC9W9W9WutZVlw6BlHTFMiUP/k48LQY=", + "owner": "ryantm", + "repo": "agenix", + "rev": "d8c973fd228949736dedf61b7f8cc1ece3236792", + "type": "github" + }, + "original": { + "owner": "ryantm", + "ref": "main", + "repo": "agenix", + "type": "github" + } + }, + "darwin": { + "inputs": { + "nixpkgs": [ + "agenix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1673295039, + "narHash": "sha256-AsdYgE8/GPwcelGgrntlijMg4t3hLFJFCRF3tL5WVjA=", + "owner": "lnl7", + "repo": "nix-darwin", + "rev": "87b9d090ad39b25b2400029c64825fc2a8868943", + "type": "github" + }, + "original": { + "owner": "lnl7", + "ref": "master", + "repo": "nix-darwin", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1673956053, + "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1690933134, + "narHash": "sha256-ab989mN63fQZBFrkk4Q8bYxQCktuHmBIBqUG1jl6/FQ=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "59cf3f1447cfc75087e7273b04b31e689a8599fb", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "ref": "main", + "repo": "flake-parts", + "type": "github" + } + }, + "futils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1689068808, + "narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=", "owner": "numtide", "repo": "flake-utils", - "rev": "2ebf2558e5bf978c7fb8ea927dfaed8fefab2e28", + "rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4", "type": "github" }, "original": { "owner": "numtide", - "ref": "master", + "ref": "main", "repo": "flake-utils", "type": "github" } }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "pre-commit-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1660459072, + "narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "a20de23b925fd8264fd7fad6454652e142fd7f73", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, "home-manager": { "inputs": { "nixpkgs": [ @@ -23,11 +131,11 @@ ] }, "locked": { - "lastModified": 1619558193, - "narHash": "sha256-DljP5/9EX0eXEPhzCUFqFEHkkcFuXJBx1PTgcv0OgyM=", + "lastModified": 1691039228, + "narHash": "sha256-iPNZJ1LvfUf1Y456ewC0DXgf99TNssG8OLObOyqxO6M=", "owner": "nix-community", "repo": "home-manager", - "rev": "18ad12d52b8cebbb57013865eec2be5125de050a", + "rev": "86dd48d70a2e2c17e84e747ba4faa92453e68d4a", "type": "github" }, "original": { @@ -39,11 +147,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1619464443, - "narHash": "sha256-R7WAb8EnkIJxxaF6GTHUPytjonhB4Zm0iatyWoW169A=", + "lastModified": 1691006197, + "narHash": "sha256-DbtxVWPt+ZP5W0Usg7jAyTomIM//c3Jtfa59Ht7AV8s=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "8e4fe32876ca15e3d5eb3ecd3ca0b224417f5f17", + "rev": "66aedfd010204949cb225cf749be08cb13ce1813", "type": "github" }, "original": { @@ -55,11 +163,11 @@ }, "nur": { "locked": { - "lastModified": 1619628114, - "narHash": "sha256-s3pQyvMfXVmbQOX224yOWQf6zi8406sShFF4u17LVQ0=", + "lastModified": 1691139289, + "narHash": "sha256-cZtqvYztpGwLtAsfrzY2VeTfFdW3HBwX7m1KV2Zy2nw=", "owner": "nix-community", "repo": "NUR", - "rev": "0615e756dc14986c4968fa478c0bd080d621cb2b", + "rev": "cb20b89d5b355c53a18dd149e7104a67381c7c17", "type": "github" }, "original": { @@ -69,12 +177,59 @@ "type": "github" } }, + "pre-commit-hooks": { + "inputs": { + "flake-compat": "flake-compat", + "flake-utils": [ + "futils" + ], + "gitignore": "gitignore", + "nixpkgs": [ + "nixpkgs" + ], + "nixpkgs-stable": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1691093055, + "narHash": "sha256-sjNWYpDHc6vx+/M0WbBZKltR0Avh2S43UiDbmYtfHt0=", + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "rev": "ebb43bdacd1af8954d04869c77bc3b61fde515e4", + "type": "github" + }, + "original": { + "owner": "cachix", + "ref": "master", + "repo": "pre-commit-hooks.nix", + "type": "github" + } + }, "root": { "inputs": { + "agenix": "agenix", + "flake-parts": "flake-parts", "futils": "futils", "home-manager": "home-manager", "nixpkgs": "nixpkgs", - "nur": "nur" + "nur": "nur", + "pre-commit-hooks": "pre-commit-hooks" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" } } }, diff --git a/flake.nix b/flake.nix index f582719..8e46ea3 100644 --- a/flake.nix +++ b/flake.nix @@ -1,11 +1,32 @@ { description = "NixOS configuration with flakes"; inputs = { + agenix = { + type = "github"; + owner = "ryantm"; + repo = "agenix"; + ref = "main"; + inputs = { + home-manager.follows = "home-manager"; + nixpkgs.follows = "nixpkgs"; + }; + }; + + flake-parts = { + type = "github"; + owner = "hercules-ci"; + repo = "flake-parts"; + ref = "main"; + inputs = { + nixpkgs-lib.follows = "nixpkgs"; + }; + }; + futils = { type = "github"; owner = "numtide"; repo = "flake-utils"; - ref = "master"; + ref = "main"; }; home-manager = { @@ -31,79 +52,20 @@ repo = "NUR"; ref = "master"; }; - }; - outputs = { self, futils, home-manager, nixpkgs, nur } @ inputs: - let - inherit (futils.lib) eachDefaultSystem; - - lib = nixpkgs.lib.extend (self: super: { - my = import ./lib { inherit inputs; pkgs = nixpkgs; lib = self; }; - }); - - defaultModules = [ - ({ ... }: { - # Let 'nixos-version --json' know about the Git revision - system.configurationRevision = self.rev or "dirty"; - }) - { - nixpkgs.overlays = (lib.attrValues self.overlays) ++ [ - nur.overlay - ]; - } - home-manager.nixosModules.home-manager - { - home-manager.users.ambroisie = import ./home; - # Nix Flakes compatibility - home-manager.useGlobalPkgs = true; - home-manager.useUserPackages = true; - } - ]; - - buildHost = name: system: lib.nixosSystem { - inherit system; - modules = defaultModules ++ [ - (./. + "/${name}.nix") - ]; - specialArgs = { - # Use my extended lib in NixOS configuration - inherit lib; - }; - }; - in - eachDefaultSystem - (system: - let - pkgs = nixpkgs.legacyPackages.${system}; - in - rec { - apps = { - diff-flake = futils.lib.mkApp { drv = packages.diff-flake; }; - }; - - defaultApp = apps.diff-flake; - - devShell = pkgs.mkShell { - name = "NixOS-config"; - buildInputs = with pkgs; [ - git-crypt - gitAndTools.pre-commit - gnupg - nixpkgs-fmt - ]; - }; - - packages = import ./pkgs { inherit pkgs; }; - }) // { - overlay = self.overlays.pkgs; - - overlays = { - lib = final: prev: { inherit lib; }; - pkgs = final: prev: { ambroisie = import ./pkgs { pkgs = prev; }; }; - }; - - nixosConfigurations = lib.mapAttrs buildHost { - porthos = "x86_64-linux"; + pre-commit-hooks = { + type = "github"; + owner = "cachix"; + repo = "pre-commit-hooks.nix"; + ref = "master"; + inputs = { + flake-utils.follows = "futils"; + nixpkgs.follows = "nixpkgs"; + nixpkgs-stable.follows = "nixpkgs"; }; }; + }; + + # Can't eta-reduce a flake outputs... + outputs = inputs: import ./flake inputs; } diff --git a/flake/apps.nix b/flake/apps.nix new file mode 100644 index 0000000..f8dc2de --- /dev/null +++ b/flake/apps.nix @@ -0,0 +1,9 @@ +{ inputs, ... }: +{ + perSystem = { self', ... }: { + apps = { + diff-flake = inputs.futils.lib.mkApp { drv = self'.packages.diff-flake; }; + default = self'.apps.diff-flake; + }; + }; +} diff --git a/flake/checks.nix b/flake/checks.nix new file mode 100644 index 0000000..98e49bd --- /dev/null +++ b/flake/checks.nix @@ -0,0 +1,33 @@ +{ inputs, ... }: +{ + imports = [ + inputs.pre-commit-hooks.flakeModule + ]; + + perSystem = { ... }: { + pre-commit = { + # Add itself to `nix flake check` + check.enable = true; + + settings = { + hooks = { + deadnix = { + enable = true; + }; + + nixpkgs-fmt = { + enable = true; + }; + + shellcheck = { + enable = true; + }; + + stylua = { + enable = true; + }; + }; + }; + }; + }; +} diff --git a/flake/default.nix b/flake/default.nix new file mode 100644 index 0000000..65102e1 --- /dev/null +++ b/flake/default.nix @@ -0,0 +1,22 @@ +{ flake-parts +, futils +, ... +} @ inputs: +let + mySystems = futils.lib.defaultSystems; +in +flake-parts.lib.mkFlake { inherit inputs; } { + systems = mySystems; + + imports = [ + ./apps.nix + ./checks.nix + ./dev-shells.nix + ./home-manager.nix + ./lib.nix + ./nixos.nix + ./overlays.nix + ./packages.nix + ./templates.nix + ]; +} diff --git a/flake/dev-shells.nix b/flake/dev-shells.nix new file mode 100644 index 0000000..d5f5989 --- /dev/null +++ b/flake/dev-shells.nix @@ -0,0 +1,19 @@ +{ ... }: +{ + perSystem = { config, pkgs, ... }: { + devShells = { + default = pkgs.mkShellNoCC { + name = "NixOS-config"; + + nativeBuildInputs = with pkgs; [ + gitAndTools.pre-commit + nixpkgs-fmt + ]; + + shellHook = '' + ${config.pre-commit.installationScript} + ''; + }; + }; + }; +} diff --git a/flake/home-manager.nix b/flake/home-manager.nix new file mode 100644 index 0000000..c55c8dd --- /dev/null +++ b/flake/home-manager.nix @@ -0,0 +1,61 @@ +{ self, inputs, lib, ... }: +let + defaultModules = [ + # Include generic settings + "${self}/home" + { + # Basic user information defaults + home.username = lib.mkDefault "ambroisie"; + home.homeDirectory = lib.mkDefault "/home/ambroisie"; + + # Make it a Linux installation by default + targets.genericLinux.enable = lib.mkDefault true; + + # Enable home-manager + programs.home-manager.enable = true; + } + ]; + + mkHome = name: system: inputs.home-manager.lib.homeManagerConfiguration { + # Work-around for home-manager + # * not letting me set `lib` as an extraSpecialArgs + # * not respecting `nixpkgs.overlays` [1] + # [1]: https://github.com/nix-community/home-manager/issues/2954 + pkgs = import inputs.nixpkgs { + inherit system; + + overlays = (lib.attrValues self.overlays) ++ [ + inputs.nur.overlay + ]; + }; + + modules = defaultModules ++ [ + "${self}/hosts/homes/${name}" + ]; + + extraSpecialArgs = { + # Inject inputs to use them in global registry + inherit inputs; + }; + }; + + hosts = { + "ambroisie@ambroisie" = "x86_64-linux"; # Unfortunate naming here... + }; +in +{ + perSystem = { system, ... }: { + # Work-around for https://github.com/nix-community/home-manager/issues/3075 + legacyPackages = { + homeConfigurations = + let + filteredHosts = lib.filterAttrs (_: v: v == system) hosts; + allHosts = filteredHosts // { + # Default configuration + ambroisie = system; + }; + in + lib.mapAttrs mkHome allHosts; + }; + }; +} diff --git a/flake/lib.nix b/flake/lib.nix new file mode 100644 index 0000000..12e89c3 --- /dev/null +++ b/flake/lib.nix @@ -0,0 +1,11 @@ +{ self, inputs, ... }: +let + inherit (inputs) nixpkgs; + + lib = nixpkgs.lib.extend (final: _: { + my = import "${self}/lib" { inherit inputs; pkgs = nixpkgs; lib = final; }; + }); +in +{ + flake.lib = lib; +} diff --git a/flake/nixos.nix b/flake/nixos.nix new file mode 100644 index 0000000..9eb6388 --- /dev/null +++ b/flake/nixos.nix @@ -0,0 +1,39 @@ +{ self, inputs, ... }: +let + inherit (self) lib; + + defaultModules = [ + { + # Let 'nixos-version --json' know about the Git revision + system.configurationRevision = self.rev or "dirty"; + } + { + nixpkgs.overlays = (lib.attrValues self.overlays) ++ [ + inputs.nur.overlay + ]; + } + # Include generic settings + "${self}/modules" + # Include bundles of settings + "${self}/profiles" + ]; + + buildHost = name: system: lib.nixosSystem { + inherit system; + modules = defaultModules ++ [ + "${self}/hosts/nixos/${name}" + ]; + specialArgs = { + # Use my extended lib in NixOS configuration + inherit lib; + # Inject inputs to use them in global registry + inherit inputs; + }; + }; +in +{ + flake.nixosConfigurations = lib.mapAttrs buildHost { + aramis = "x86_64-linux"; + porthos = "x86_64-linux"; + }; +} diff --git a/flake/overlays.nix b/flake/overlays.nix new file mode 100644 index 0000000..0c47989 --- /dev/null +++ b/flake/overlays.nix @@ -0,0 +1,17 @@ +{ self, ... }: +let + default-overlays = import "${self}/overlays"; + + additional-overlays = { + # Expose my expanded library + lib = _final: _prev: { inherit (self) lib; }; + + # Expose my custom packages + pkgs = _final: prev: { + ambroisie = prev.recurseIntoAttrs (import "${self}/pkgs" { pkgs = prev; }); + }; + }; +in +{ + flake.overlays = default-overlays // additional-overlays; +} diff --git a/flake/packages.nix b/flake/packages.nix new file mode 100644 index 0000000..3515071 --- /dev/null +++ b/flake/packages.nix @@ -0,0 +1,13 @@ +{ self, inputs, ... }: +{ + perSystem = { pkgs, system, ... }: { + packages = + let + inherit (inputs.futils.lib) filterPackages flattenTree; + packages = import "${self}/pkgs" { inherit pkgs; }; + flattenedPackages = flattenTree packages; + finalPackages = filterPackages system flattenedPackages; + in + finalPackages; + }; +} diff --git a/flake/templates.nix b/flake/templates.nix new file mode 100644 index 0000000..28e534e --- /dev/null +++ b/flake/templates.nix @@ -0,0 +1,4 @@ +{ self, ... }: +{ + flake.templates = import "${self}/templates"; +} diff --git a/home/aliases/default.nix b/home/aliases/default.nix new file mode 100644 index 0000000..259f148 --- /dev/null +++ b/home/aliases/default.nix @@ -0,0 +1,26 @@ +{ config, lib, ... }: +let + cfg = config.my.home.aliases; +in +{ + options.my.home.aliases = with lib; { + enable = my.mkDisableOption "shell aliases configuration"; + }; + + config = lib.mkIf cfg.enable { + home = { + shellAliases = { + # I like pretty colors + diff = "diff --color=auto"; + grep = "grep --color=auto"; + egrep = "egrep --color=auto"; + fgrep = "fgrep --color=auto"; + ls = "ls --color=auto"; + + # Well-known ls aliases + l = "ls -alh"; + ll = "ls -l"; + }; + }; + }; +} diff --git a/home/atuin/default.nix b/home/atuin/default.nix new file mode 100644 index 0000000..19a6fb9 --- /dev/null +++ b/home/atuin/default.nix @@ -0,0 +1,31 @@ +{ config, lib, ... }: +let + cfg = config.my.home.atuin; +in +{ + options.my.home.atuin = with lib; { + enable = my.mkDisableOption "atuin configuration"; + }; + + config = lib.mkIf cfg.enable { + programs.atuin = { + enable = true; + + flags = [ + # I *despise* this hijacking of the up key, even though I use Ctrl-p + "--disable-up-arrow" + ]; + + settings = { + # The package is managed by Nix + update_check = false; + # I don't care for the fancy display + style = "compact"; + # Get closer to fzf's fuzzy search + search_mode = "skim"; + # Show long command lines at the bottom + show_preview = true; + }; + }; + }; +} diff --git a/home/bat.nix b/home/bat/default.nix similarity index 65% rename from home/bat.nix rename to home/bat/default.nix index 8485dd3..67d80c2 100644 --- a/home/bat.nix +++ b/home/bat/default.nix @@ -3,13 +3,15 @@ let cfg = config.my.home.bat; in { - options.my.home.bat = with lib.my; { - enable = mkDisableOption "bat configuration"; + options.my.home.bat = with lib; { + enable = my.mkDisableOption "bat configuration"; }; config.programs.bat = lib.mkIf cfg.enable { enable = true; config = { + theme = "gruvbox-dark"; + pager = with config.home.sessionVariables; "${PAGER} ${LESS}"; }; }; diff --git a/home/bitwarden/default.nix b/home/bitwarden/default.nix new file mode 100644 index 0000000..c709f7b --- /dev/null +++ b/home/bitwarden/default.nix @@ -0,0 +1,27 @@ +{ config, lib, ... }: +let + cfg = config.my.home.bitwarden; +in +{ + options.my.home.bitwarden = with lib; { + enable = my.mkDisableOption "bitwarden configuration"; + + pinentry = mkOption { + type = types.str; + default = "tty"; + example = "gtk2"; + description = "Which pinentry interface to use"; + }; + }; + + config = lib.mkIf cfg.enable { + programs.rbw = { + enable = true; + + settings = { + email = lib.my.mkMailAddress "bruno" "belanyi.fr"; + inherit (cfg) pinentry; + }; + }; + }; +} 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/calibre/default.nix b/home/calibre/default.nix new file mode 100644 index 0000000..e0f2069 --- /dev/null +++ b/home/calibre/default.nix @@ -0,0 +1,15 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.home.calibre; +in +{ + options.my.home.calibre = with lib; { + enable = mkEnableOption "calibre configuration"; + }; + + config = lib.mkIf cfg.enable { + home.packages = with pkgs; [ + calibre # NOTE: relies on my overlay to add necessary plug-in dependencies + ]; + }; +} 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..8ba3a8d 100644 --- a/home/default.nix +++ b/home/default.nix @@ -1,20 +1,47 @@ { ... }: { imports = [ - ./bat.nix - ./direnv.nix - ./documentation.nix + ./aliases + ./atuin + ./bat + ./bitwarden + ./bluetooth + ./calibre + ./comma + ./dircolors + ./direnv + ./discord + ./documentation + ./feh + ./firefox + ./flameshot + ./fzf + ./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 + ./nix-index + ./nixpkgs + ./nm-applet + ./packages + ./pager + ./power-alert + ./secrets + ./ssh + ./terminal + ./tmux + ./udiskie ./vim - ./xdg.nix + ./wm + ./x + ./xdg + ./zathura ./zsh ]; @@ -23,4 +50,7 @@ # Who am I? home.username = "ambroisie"; + + # Start services automatically + systemd.user.startServices = "sd-switch"; } diff --git a/home/dircolors/default.nix b/home/dircolors/default.nix new file mode 100644 index 0000000..876b413 --- /dev/null +++ b/home/dircolors/default.nix @@ -0,0 +1,15 @@ +{ config, lib, ... }: +let + cfg = config.my.home.dircolors; +in +{ + options.my.home.dircolors = with lib; { + enable = my.mkDisableOption "dircolors configuration"; + }; + + config = lib.mkIf cfg.enable { + programs.dircolors = { + enable = true; + }; + }; +} 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 72% rename from home/documentation.nix rename to home/documentation/default.nix index e31665f..1a305e0 100644 --- a/home/documentation.nix +++ b/home/documentation/default.nix @@ -3,8 +3,8 @@ let cfg = config.my.home.documentation; in { - options.my.home.documentation = with lib.my; { - enable = mkDisableOption "documentation integration"; + options.my.home.documentation = with lib; { + enable = my.mkDisableOption "documentation integration"; }; # Add documentation for user packages 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..7374b63 --- /dev/null +++ b/home/firefox/default.nix @@ -0,0 +1,83 @@ +{ 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.enabled" = true; # Enable DRM + "media.gmp-widevinecdm.visible" = 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 + 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..35b58c2 --- /dev/null +++ b/home/firefox/tridactyl/default.nix @@ -0,0 +1,28 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.home.firefox.tridactyl; + + term = config.my.home.terminal.program; + + vimCommandLine = { + alacritty = ''-e "vim" "%f" "+normal!%lGzv%c|"''; + # Termite wants the whole command in a single argument... + termite = ''-e "vim %f '+normal!%lGzv%c|'"''; + }; +in +{ + config = lib.mkIf cfg.enable { + xdg.configFile."tridactyl/tridactylrc".source = pkgs.substituteAll { + src = ./tridactylrc; + + editorcmd = lib.concatStringsSep " " [ + # Use my configured terminal + term + # Make it easy to pick out with a window class name + "--class tridactyl_editor" + # Open vim with the cursor in the correct position + vimCommandLine.${term} + ]; + }; + }; +} diff --git a/home/firefox/tridactyl/tridactylrc b/home/firefox/tridactyl/tridactylrc new file mode 100644 index 0000000..31d3cb7 --- /dev/null +++ b/home/firefox/tridactyl/tridactylrc @@ -0,0 +1,82 @@ +" 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 +set editorcmd @editorcmd@ + +" Remove editor file after use +alias editor_rm composite editor | jsb -p tri.native.run(`rm -f '${JS_ARG[0]}'`) +bind --mode=insert editor_rm +bind --mode=input editor_rm +" }}} + +" 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 [data-testid="result-title-a"] +bindurl ^https://duckduckgo.com F hint -Jbc [data-testid="result-title-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/fzf/default.nix b/home/fzf/default.nix new file mode 100644 index 0000000..b7e308f --- /dev/null +++ b/home/fzf/default.nix @@ -0,0 +1,15 @@ +{ config, lib, ... }: +let + cfg = config.my.home.fzf; +in +{ + options.my.home.fzf = with lib; { + enable = my.mkDisableOption "fzf configuration"; + }; + + config = lib.mkIf cfg.enable { + programs.fzf = { + 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..d80996e 100644 --- a/home/git/default.ignore +++ b/home/git/default.ignore @@ -24,3 +24,11 @@ compile_commands.json # Swap and backup files *~ ~.swp + +# Direnv files +.envrc +.direnv/ + +# Project-local neovim configuration files +.nvim.lua +.nvimrc diff --git a/home/git/default.nix b/home/git/default.nix index da5efae..4dba01e 100644 --- a/home/git/default.nix +++ b/home/git/default.nix @@ -1,34 +1,73 @@ { 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"; + options.my.home.git = with lib; { + enable = my.mkDisableOption "git configuration"; }; + config.home.packages = with pkgs; lib.mkIf cfg.enable [ + git-absorb + git-revise + 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; + package = pkgs.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' ')"''; + root = "git rev-parse --show-toplevel"; }; 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 +101,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 +121,14 @@ 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"; - }; + # Local configuration, not-versioned + include = { + path = "config.local"; + }; + + merge = { + conflictStyle = "zdiff3"; + }; pull = { # Avoid useless merge commits @@ -114,11 +145,42 @@ in autoSquash = true; autoStash = true; }; + + url = { + "git@git.belanyi.fr:" = { + insteadOf = "https://git.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"; + }; + }; + } + { + condition = "gitdir:~/git/work/"; + contents = { + user = { + name = "Bruno BELANYI"; + email = mkMailAddress "ambroisie" "google.com"; + }; + }; + } ]; 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.nix deleted file mode 100644 index 548f90b..0000000 --- a/home/gpg.nix +++ /dev/null @@ -1,24 +0,0 @@ -{ config, lib, ... }: -let - cfg = config.my.home.gpg; -in -{ - options.my.home.gpg = with lib.my; { - enable = mkDisableOption "gpg configuration"; - }; - - config = lib.mkIf cfg.enable { - programs.gpg = { - enable = true; - }; - - services.gpg-agent = { - enable = true; - enableSshSupport = true; # One agent to rule them all - pinentryFlavor = "tty"; - extraConfig = '' - allow-loopback-pinentry - ''; - }; - }; -} diff --git a/home/gpg/default.nix b/home/gpg/default.nix new file mode 100644 index 0000000..7eadf48 --- /dev/null +++ b/home/gpg/default.nix @@ -0,0 +1,36 @@ +{ config, lib, ... }: +let + cfg = config.my.home.gpg; +in +{ + 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 { + programs.gpg = { + enable = true; + }; + + services.gpg-agent = { + enable = true; + enableSshSupport = true; # One agent to rule them all + pinentryFlavor = cfg.pinentry; + extraConfig = '' + allow-loopback-pinentry + ''; + }; + + home.shellAliases = { + # Sometime `gpg-agent` errors out... + reset-agent = "gpg-connect-agent updatestartuptty /bye"; + }; + }; +} 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 60% rename from home/htop.nix rename to home/htop/default.nix index c4b33a9..570fa47 100644 --- a/home/htop.nix +++ b/home/htop/default.nix @@ -3,8 +3,8 @@ let cfg = config.my.home.htop; in { - options.my.home.htop = with lib.my; { - enable = mkDisableOption "htop configuration"; + options.my.home.htop = with lib; { + enable = my.mkDisableOption "htop configuration"; }; config.programs.htop = lib.mkIf cfg.enable { diff --git a/home/jq.nix b/home/jq/default.nix similarity index 78% rename from home/jq.nix rename to home/jq/default.nix index d8bd59b..57e266f 100644 --- a/home/jq.nix +++ b/home/jq/default.nix @@ -3,8 +3,8 @@ let cfg = config.my.home.jq; in { - options.my.home.jq = with lib.my; { - enable = mkDisableOption "jq configuration"; + options.my.home.jq = with lib; { + enable = my.mkDisableOption "jq configuration"; }; config.programs.jq = lib.mkIf cfg.enable { diff --git a/home/mail/accounts/default.nix b/home/mail/accounts/default.nix new file mode 100644 index 0000000..e7663d8 --- /dev/null +++ b/home/mail/accounts/default.nix @@ -0,0 +1,94 @@ +{ 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 [ (lib.getExe pkgs.ambroisie.rbw-pass) "Mail" passName ]; + + address = mkMailAddress address domain; + aliases = builtins.map (lib.flip mkMailAddress domain) aliases; + + inherit primary; + + himalaya = { + enable = cfg.himalaya.enable; + # FIXME: try to actually configure it at some point + backend = "imap"; + sender = "smtp"; + }; + + 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..14f086a --- /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 = mkEnableOption "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..849a415 --- /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 = lib.getExe pkgs.libnotify; + 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..383621e --- /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; { + enable = my.mkDisableOption "nix-index configuration"; + }; + + config.programs.nix-index = lib.mkIf cfg.enable { + enable = true; + }; +} diff --git a/home/nix/default.nix b/home/nix/default.nix new file mode 100644 index 0000000..9ccbdc5 --- /dev/null +++ b/home/nix/default.nix @@ -0,0 +1,82 @@ +# Nix related settings +{ config, inputs, lib, pkgs, ... }: +let + cfg = config.my.home.nix; + + channels = lib.my.merge [ + { + # Allow me to use my custom package using `nix run self#pkg` + self = inputs.self; + # Add NUR to run some packages that are only present there + nur = inputs.nur; + # Use pinned nixpkgs when using `nix run pkgs#` + pkgs = inputs.nixpkgs; + } + (lib.optionalAttrs cfg.overrideNixpkgs { + # ... And with `nix run nixpkgs#` + nixpkgs = inputs.nixpkgs; + }) + ]; +in +{ + options.my.home.nix = with lib; { + enable = my.mkDisableOption "nix configuration"; + + linkInputs = my.mkDisableOption "link inputs to `$XDG_CONFIG_HOME/nix/inputs`"; + + addToRegistry = my.mkDisableOption "add inputs and self to registry"; + + addToNixPath = my.mkDisableOption "add inputs and self to nix path"; + + overrideNixpkgs = my.mkDisableOption "point nixpkgs to pinned system version"; + }; + + config = lib.mkIf cfg.enable (lib.mkMerge [ + { + assertions = [ + { + assertion = cfg.addToNixPath -> cfg.linkInputs; + message = '' + enabling `my.home.nix.addToNixPath` needs to have + `my.home.nix.linkInputs = true` + ''; + } + ]; + } + + { + nix = { + package = lib.mkDefault pkgs.nix; # NixOS module sets it unconditionally + + settings = { + experimental-features = [ "nix-command" "flakes" ]; + }; + }; + } + + (lib.mkIf cfg.addToRegistry { + nix.registry = + let + makeEntry = v: { flake = v; }; + makeEntries = lib.mapAttrs (lib.const makeEntry); + in + makeEntries channels; + }) + + (lib.mkIf cfg.linkInputs { + xdg.configFile = + let + makeLink = n: v: { + name = "nix/inputs/${n}"; + value = { source = v.outPath; }; + }; + makeLinks = lib.mapAttrs' makeLink; + in + makeLinks channels; + }) + + (lib.mkIf cfg.addToNixPath { + home.sessionVariables.NIX_PATH = "${config.xdg.configHome}/nix/inputs\${NIX_PATH:+:$NIX_PATH}"; + }) + ]); +} diff --git a/home/nixpkgs/default.nix b/home/nixpkgs/default.nix new file mode 100644 index 0000000..720fc9b --- /dev/null +++ b/home/nixpkgs/default.nix @@ -0,0 +1,20 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.home.nixpkgs; +in +{ + options.my.home.nixpkgs = with lib; { + enable = mkEnableOption "nixpkgs configuration"; + }; + + config = lib.mkIf cfg.enable { + home.packages = with pkgs; [ + nixpkgs-review + ]; + + home.sessionVariables = { + GITHUB_TOKEN = ''$(cat "${config.age.secrets."github/token".path}")''; + GITHUB_API_TOKEN = ''$(cat "${config.age.secrets."github/token".path}")''; + }; + }; +} 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..0cfa3b3 --- /dev/null +++ b/home/packages/default.nix @@ -0,0 +1,26 @@ +{ 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 + ] ++ 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..aa72587 --- /dev/null +++ b/home/pager/default.nix @@ -0,0 +1,21 @@ +{ config, lib, ... }: +let + cfg = config.my.home.pager; +in +{ + options.my.home.pager = with lib; { + enable = my.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"; + # Better XDG compliance + LESSHISTFILE = "${config.xdg.dataHome}/less/history"; + }; + }; +} 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 index 3624472..7c0c0a1 100644 --- a/home/secrets/default.nix +++ b/home/secrets/default.nix @@ -1,19 +1,25 @@ -{ lib, ... }: +{ config, inputs, lib, options, ... }: -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; - }; +{ + imports = [ + inputs.agenix.homeManagerModules.age + ]; - config.my.secrets = { - # Home-manager secrets go here + config.age = { + secrets = + let + toName = lib.removeSuffix ".age"; + toSecret = name: { ... }: { + file = ./. + "/${name}"; + }; + convertSecrets = n: v: lib.nameValuePair (toName n) (toSecret n v); + secrets = import ./secrets.nix; + in + lib.mapAttrs' convertSecrets secrets; + + # Add my usual agenix key to the defaults + identityPaths = options.age.identityPaths.default ++ [ + "${config.home.homeDirectory}/.ssh/agenix" + ]; }; } diff --git a/home/secrets/github/token.age b/home/secrets/github/token.age new file mode 100644 index 0000000..1d36ccd Binary files /dev/null and b/home/secrets/github/token.age differ diff --git a/home/secrets/secrets.nix b/home/secrets/secrets.nix new file mode 100644 index 0000000..f474342 --- /dev/null +++ b/home/secrets/secrets.nix @@ -0,0 +1,9 @@ +# Common secrets +let + keys = import ../../keys; + + all = builtins.attrValues keys.users; +in +{ + "github/token.age".publicKeys = all; +} diff --git a/home/ssh.nix b/home/ssh/default.nix similarity index 73% rename from home/ssh.nix rename to home/ssh/default.nix index 22f85d3..123190f 100644 --- a/home/ssh.nix +++ b/home/ssh/default.nix @@ -3,13 +3,18 @@ let cfg = config.my.home.ssh; in { - options.my.home.ssh = with lib.my; { - enable = mkDisableOption "ssh configuration"; + options.my.home.ssh = with lib; { + enable = my.mkDisableOption "ssh configuration"; }; - config.programs.ssh = { + config.programs.ssh = lib.mkIf cfg.enable { enable = true; + includes = [ + # Local configuration, not-versioned + "config.local" + ]; + matchBlocks = { "github.com" = { hostname = "github.com"; @@ -29,8 +34,8 @@ in user = "git"; }; - "gitea.belanyi.fr" = { - hostname = "gitea.belanyi.fr"; + "git.belanyi.fr" = { + hostname = "git.belanyi.fr"; identityFile = "~/.ssh/shared_rsa"; user = "git"; }; diff --git a/home/terminal/alacritty/default.nix b/home/terminal/alacritty/default.nix new file mode 100644 index 0000000..daf3e80 --- /dev/null +++ b/home/terminal/alacritty/default.nix @@ -0,0 +1,52 @@ +{ config, lib, ... }: +let + cfg = config.my.home.terminal; +in +{ + config = lib.mkIf (cfg.program == "alacritty") { + programs.alacritty = { + enable = true; + + settings = { + font = { + size = 5.5; + }; + + colors = { + primary = { + background = cfg.colors.background; + foreground = cfg.colors.foreground; + + bright_foreground = cfg.colors.foregroundBold; + }; + + cursor = { + cursor = cfg.colors.cursor; + }; + + normal = { + black = cfg.colors.black; + red = cfg.colors.red; + green = cfg.colors.green; + yellow = cfg.colors.yellow; + blue = cfg.colors.blue; + magenta = cfg.colors.magenta; + cyan = cfg.colors.cyan; + white = cfg.colors.white; + }; + + bright = { + black = cfg.colors.blackBold; + red = cfg.colors.redBold; + green = cfg.colors.greenBold; + yellow = cfg.colors.yellowBold; + blue = cfg.colors.blueBold; + magenta = cfg.colors.magentaBold; + cyan = cfg.colors.cyanBold; + white = cfg.colors.whiteBold; + }; + }; + }; + }; + }; +} diff --git a/home/terminal/default.nix b/home/terminal/default.nix new file mode 100644 index 0000000..20f36b5 --- /dev/null +++ b/home/terminal/default.nix @@ -0,0 +1,62 @@ +{ 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 = [ + ./alacritty + ./termite + ]; + + options.my.home = with lib; { + terminal = { + program = mkOption { + type = with types; nullOr (enum [ "alacritty" "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.nix deleted file mode 100644 index 6b05414..0000000 --- a/home/tmux.nix +++ /dev/null @@ -1,47 +0,0 @@ -{ config, lib, pkgs, ... }: -let - cfg = config.my.home.tmux; -in -{ - options.my.home.tmux = with lib.my; { - enable = mkDisableOption "tmux terminal multiplexer"; - }; - - config.programs.tmux = lib.mkIf cfg.enable { - enable = true; - - 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 - terminal = "tmux-256color"; # I want accurate termcap info - - plugins = with pkgs.tmuxPlugins; [ - # Open high-lighted files in copy mode - open - # Better pane management - pain-control - # Better session management - sessionist - # X clipboard integration - yank - { - # Show when prefix has been pressed - plugin = prefix-highlight; - extraConfig = '' - # Also show when I'm in copy or sync mode - set -g @prefix_highlight_show_copy_mode 'on' - set -g @prefix_highlight_show_sync_mode 'on' - # Show prefix mode in status bar - set -g status-right '#{prefix_highlight} %a %Y-%m-%d %H:%M' - ''; - } - ]; - - 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 - ''; - }; -} diff --git a/home/tmux/default.nix b/home/tmux/default.nix new file mode 100644 index 0000000..70f037f --- /dev/null +++ b/home/tmux/default.nix @@ -0,0 +1,80 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.home.tmux; + hasGUI = lib.any lib.id [ + config.my.home.x.enable + (config.my.home.wm.windowManager != null) + ]; +in +{ + options.my.home.tmux = with lib; { + enable = my.mkDisableOption "tmux terminal multiplexer"; + + enabledPassthrough = mkEnableOption "tmux DCS passthrough sequence"; + }; + + config.programs.tmux = lib.mkIf cfg.enable { + enable = true; + + 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 = 50000; # Bigger buffer + terminal = "tmux-256color"; # I want accurate termcap info + + plugins = with pkgs.tmuxPlugins; [ + # Open high-lighted files in copy mode + open + # Better pane management + pain-control + # Better session management + sessionist + { + # 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; + extraConfig = '' + # Also show when I'm in copy or sync mode + set -g @prefix_highlight_show_copy_mode 'on' + set -g @prefix_highlight_show_sync_mode 'on' + # Show prefix mode in status bar + set -g status-right '#{prefix_highlight} %a %Y-%m-%d %H:%M' + ''; + } + ]; + + extraConfig = '' + # Better vim mode + bind-key -T copy-mode-vi 'v' send -X begin-selection + ${ + 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 + + # Allow any application to send OSC52 escapes to set the clipboard + set -s set-clipboard on + + # Longer session names in status bar + set -g status-left-length 16 + + ${ + lib.optionalString cfg.enabledPassthrough '' + # Allow any application to use the tmux DCS for passthrough + set -g allow-passthrough on + '' + } + ''; + }; +} 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/markdown.vim similarity index 68% rename from home/vim/after/ftplugin/pandoc.vim rename to home/vim/after/ftplugin/markdown.vim index d44bc12..5b58b41 100644 --- a/home/vim/after/ftplugin/pandoc.vim +++ b/home/vim/after/ftplugin/markdown.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/nix.vim b/home/vim/after/ftplugin/nix.vim new file mode 100644 index 0000000..fb6126e --- /dev/null +++ b/home/vim/after/ftplugin/nix.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 Nix 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.lua b/home/vim/after/plugin/mappings/commentary.lua new file mode 100644 index 0000000..6ed3b89 --- /dev/null +++ b/home/vim/after/plugin/mappings/commentary.lua @@ -0,0 +1,10 @@ +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" }) 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.lua b/home/vim/after/plugin/mappings/misc.lua new file mode 100644 index 0000000..6aa25a2 --- /dev/null +++ b/home/vim/after/plugin/mappings/misc.lua @@ -0,0 +1,7 @@ +local wk = require("which-key") + +local keys = { + [""] = { "nohls", "Clear search highlight" }, +} + +wk.register(keys, { prefix = "" }) diff --git a/home/vim/after/plugin/mappings/misc.vim b/home/vim/after/plugin/mappings/misc.vim deleted file mode 100644 index 7f1ea1c..0000000 --- a/home/vim/after/plugin/mappings/misc.vim +++ /dev/null @@ -1,8 +0,0 @@ -" Yank until the end of line with Y, to be more consistent with D and C -nnoremap Y y$ - -" Run make silently, then skip the 'Press ENTER to continue' -noremap m :silent! :make! \| :redraw! - -" Remove search-highlighting -noremap :nohls 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.lua b/home/vim/after/plugin/mappings/telescope.lua new file mode 100644 index 0000000..e0d38a7 --- /dev/null +++ b/home/vim/after/plugin/mappings/telescope.lua @@ -0,0 +1,17 @@ +local wk = require("which-key") +local telescope = require("telescope") +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" }, + n = { telescope.extensions.notify.notify, "Notification history" }, + }, +} + +wk.register(keys, { prefix = "" }) diff --git a/home/vim/after/plugin/mappings/tree-sitter-textobjects.lua b/home/vim/after/plugin/mappings/tree-sitter-textobjects.lua new file mode 100644 index 0000000..631731c --- /dev/null +++ b/home/vim/after/plugin/mappings/tree-sitter-textobjects.lua @@ -0,0 +1,30 @@ +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" }) diff --git a/home/vim/after/plugin/mappings/unimpaired.lua b/home/vim/after/plugin/mappings/unimpaired.lua new file mode 100644 index 0000000..f502056 --- /dev/null +++ b/home/vim/after/plugin/mappings/unimpaired.lua @@ -0,0 +1,128 @@ +local wk = require("which-key") + +local lsp = require("ambroisie.lsp") + +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 = { lsp.goto_prev_diagnostic, "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 = { lsp.goto_next_diagnostic, "Next diagnostic" }, + }, + + -- Option mappings + ["[o"] = { + name = "Enable option", + b = "Light background", + c = "Cursor line", + d = "Diff", + f = { "FormatEnable", "LSP Formatting" }, + h = "Search high-lighting", + i = "Case insensitive search", + l = "List mode", + n = "Line numbers", + r = "Relative line numbers", + p = { "lwindow", "Location list" }, + q = { "cwindow", "Quickfix list" }, + 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", + f = { "FormatDisable", "LSP Formatting" }, + h = "Search high-lighting", + i = "Case insensitive search", + l = "List mode", + n = "Line numbers", + p = { "lclose", "Location list" }, + q = { "cclose", "Quickfix list" }, + 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", + f = { "FormatToggle", "LSP Formatting" }, + h = "Search high-lighting", + i = "Case insensitive search", + l = "List mode", + n = "Line numbers", + p = { "(qf_loc_toggle)", "Location list" }, + q = { "(qf_qf_toggle)", "Quickfix list" }, + r = "Relative line numbers", + u = "Cursor column", + v = "Virtual editing", + w = "Text wrapping", + x = "Cursor line and column", + z = "Spell checking", + }, +} + +wk.register(keys) diff --git a/home/vim/after/plugin/mappings/unimpaired.vim b/home/vim/after/plugin/mappings/unimpaired.vim deleted file mode 100644 index 53457bd..0000000 --- a/home/vim/after/plugin/mappings/unimpaired.vim +++ /dev/null @@ -1,7 +0,0 @@ -" Better fr layout mappings for vim-unimpaired and other '[' and ']' commands -nmap ( [ -nmap ) ] -omap ( [ -omap ) ] -xmap ( [ -xmap ) ] 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..3bd2469 100644 --- a/home/vim/default.nix +++ b/home/vim/default.nix @@ -13,16 +13,21 @@ let "after" "autoload" "ftdetect" + "lua" "plugin" ]; in { - options.my.home.vim = with lib.my; { - enable = mkDisableOption "vim configuration"; + options.my.home.vim = with lib; { + enable = my.mkDisableOption "vim configuration"; }; config.programs.neovim = lib.mkIf cfg.enable { enable = true; + + # This is the best editor + defaultEditor = true; + # All the aliases viAlias = true; vimAlias = true; @@ -30,11 +35,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... - } + gruvbox-nvim # 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 +46,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 @@ -52,25 +54,61 @@ in vim-beancount vim-jsonnet vim-nix - vim-pandoc - vim-pandoc-syntax vim-toml # General enhancements - fastfold # Better folding vim-qf # Better quick-fix list + nvim-osc52 # Send clipboard data through terminal escape for SSH # 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-format-nvim # Simplified formatting configuration + lsp_lines-nvim # Show diagnostics *over* regions + null-ls-nvim # LSP integration for linters and formatters + nvim-treesitter.withAllGrammars # Better highlighting + nvim-treesitter-textobjects # More textobjects + nvim-ts-context-commentstring # Comment string in nested language blocks + plenary-nvim # 'null-ls', 'telescope' dependency + + # Completion + luasnip # Snippet manager compatible with LSP + friendly-snippets # LSP snippets collection + 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 + cmp_luasnip # Snippet suggestions from LuaSnip + + # UX improvements + dressing-nvim # Integrate native UI hooks with Telescope etc... + gitsigns-nvim # Fast git UI integration + nvim-notify # Better notification API + 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/automake.lua b/home/vim/ftdetect/automake.lua new file mode 100644 index 0000000..cfa15d2 --- /dev/null +++ b/home/vim/ftdetect/automake.lua @@ -0,0 +1,6 @@ +-- Use Automake filetype for `local.am` files, explicit `set` to force override +vim.filetype.add({ + filename = { + ["local.am"] = "automake", + }, +}) diff --git a/home/vim/ftdetect/automake.vim b/home/vim/ftdetect/automake.vim deleted file mode 100644 index 5cc73b0..0000000 --- a/home/vim/ftdetect/automake.vim +++ /dev/null @@ -1,2 +0,0 @@ -" Use Automake filetype for `local.am` files, explicit `set` to force override -au BufNewFile,BufRead local.am set filetype=automake diff --git a/home/vim/ftdetect/direnv.lua b/home/vim/ftdetect/direnv.lua new file mode 100644 index 0000000..fba9748 --- /dev/null +++ b/home/vim/ftdetect/direnv.lua @@ -0,0 +1,6 @@ +-- Use bash filetype for `.envrc` files +vim.filetype.add({ + filename = { + [".envrc"] = "bash", + }, +}) diff --git a/home/vim/ftdetect/kbuild.lua b/home/vim/ftdetect/kbuild.lua new file mode 100644 index 0000000..799570e --- /dev/null +++ b/home/vim/ftdetect/kbuild.lua @@ -0,0 +1,6 @@ +-- Kbuild is just a Makefile under a different name +vim.filetype.add({ + filename = { + ["Kbuild"] = "make", + }, +}) diff --git a/home/vim/ftdetect/kconfig.lua b/home/vim/ftdetect/kconfig.lua new file mode 100644 index 0000000..d51e964 --- /dev/null +++ b/home/vim/ftdetect/kconfig.lua @@ -0,0 +1,6 @@ +-- Mconfig is just Kconfig +vim.filetype.add({ + filename = { + ["Mconfig"] = "kconfig", + }, +}) diff --git a/home/vim/ftdetect/tiger.lua b/home/vim/ftdetect/tiger.lua new file mode 100644 index 0000000..a261103 --- /dev/null +++ b/home/vim/ftdetect/tiger.lua @@ -0,0 +1,7 @@ +-- Use Tiger filetype for programs and header files +vim.filetype.add({ + extension = { + tig = "tiger", + tih = "tiger", + }, +}) diff --git a/home/vim/ftdetect/tikz.lua b/home/vim/ftdetect/tikz.lua new file mode 100644 index 0000000..93b7db0 --- /dev/null +++ b/home/vim/ftdetect/tikz.lua @@ -0,0 +1,6 @@ +-- Use LaTeX filetype for TikZ files +vim.filetype.add({ + extension = { + tikz = "tex", + }, +}) diff --git a/home/vim/ftdetect/tikz.vim b/home/vim/ftdetect/tikz.vim deleted file mode 100644 index 7327b11..0000000 --- a/home/vim/ftdetect/tikz.vim +++ /dev/null @@ -1,2 +0,0 @@ -" Use LaTeX filetype for TikZ files -au BufNewFile,BufRead *.tikz setfiletype tex diff --git a/home/vim/init.vim b/home/vim/init.vim index 8968204..bd63d25 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 {{{ @@ -59,19 +66,30 @@ set list " Show a tab as an arrow, trailing spaces as ¤, non-breaking spaces as dots set listchars=tab:>─,trail:·,nbsp:¤ +" Use patience diff +set diffopt+=algorithm:patience +" Align similar lines in each hunk +set diffopt+=linematch:50 + " Don't redraw when executing macros 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 + +" Disable all mouse integrations +set mouse= + " Set dark mode by default set background=dark -" Load it manually because of autoload functions... -packadd! onedark-vim -" Use onedark -colorscheme onedark +" 24 bit colors +set termguicolors +" Use my preferred colorscheme +colorscheme gruvbox " }}} " Search parameters {{{ @@ -84,12 +102,10 @@ set ignorecase set smartcase " }}} -" Import settings when inside a git repository {{{ +" Project-local settings {{{ """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" -let git_settings=system("git config --get vim.settings") -if strlen(git_settings) - exe "set" git_settings -endif +" Securely read `.nvim.lua` or `.nvimrc`. +set exrc " }}} " vim: foldmethod=marker diff --git a/home/vim/lua/ambroisie/lsp.lua b/home/vim/lua/ambroisie/lsp.lua new file mode 100644 index 0000000..7ef6e26 --- /dev/null +++ b/home/vim/lua/ambroisie/lsp.lua @@ -0,0 +1,118 @@ +local M = {} + +-- Simplified LSP formatting configuration +local lsp_format = require("lsp-format") + +-- Move to the next/previous diagnostic, automatically showing the diagnostics +-- float if necessary. +-- @param forward whether to go forward or backwards +local function goto_diagnostic(forward) + vim.validate({ + forward = { forward, "boolean" }, + }) + + local opts = { + float = false, + } + + -- Only show floating diagnostics if they are otherwise not displayed + local config = vim.diagnostic.config() + if not (config.virtual_text or config.virtual_lines) then + opts.float = true + end + + if forward then + vim.diagnostic.goto_next(opts) + else + vim.diagnostic.goto_prev(opts) + end +end + +-- Move to the next diagnostic, automatically showing the diagnostics float if +-- necessary. +M.goto_next_diagnostic = function() + goto_diagnostic(true) +end + +-- Move to the previous diagnostic, automatically showing the diagnostics float +-- if necessary. +M.goto_prev_diagnostic = function() + goto_diagnostic(false) +end + +-- shared LSP configuration callback +-- @param client native client configuration +-- @param bufnr int? buffer number of the attched client +M.on_attach = function(client, bufnr) + -- Format on save + lsp_format.on_attach(client, bufnr) + + -- 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 cycle_diagnostics_display() + -- Cycle from: + -- * nothing displayed + -- * single diagnostic at the end of the line (`virtual_text`) + -- * full diagnostics using virtual text (`virtual_lines`) + local text = vim.diagnostic.config().virtual_text + local lines = vim.diagnostic.config().virtual_lines + + -- Text -> Lines transition + if text then + text = false + lines = true + -- Lines -> Nothing transition + elseif lines then + text = false + lines = false + -- Nothing -> Text transition + else + text = true + lines = false + end + + vim.diagnostic.config({ + virtual_text = text, + virtual_lines = lines, + }) + 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 = { cycle_diagnostics_display, "Cycle diagnostics display" }, + 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..984c730 --- /dev/null +++ b/home/vim/lua/ambroisie/utils.lua @@ -0,0 +1,57 @@ +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 + +-- whether or not we are currently in an SSH connection +-- @return boolean ssh connection +M.is_ssh = function() + local variables = { + "SSH_CONNECTION", + "SSH_CLIENT", + "SSH_TTY", + } + + for _, var in ipairs(variables) do + if string.len(os.getenv(var) or "") ~= 0 then + return true + end + end + + return false +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/abbreviations.lua b/home/vim/plugin/abbreviations.lua new file mode 100644 index 0000000..f6d6ac3 --- /dev/null +++ b/home/vim/plugin/abbreviations.lua @@ -0,0 +1,9 @@ +local abbreviations = { + -- A few things that are hard to write in ASCII + ["(R)"] = "©", + ["(TM)"] = "â„¢", +} + +for text, result in pairs(abbreviations) do + vim.cmd.abbreviate(text, result) +end diff --git a/home/vim/plugin/abbreviations.vim b/home/vim/plugin/abbreviations.vim deleted file mode 100644 index 5d36434..0000000 --- a/home/vim/plugin/abbreviations.vim +++ /dev/null @@ -1,5 +0,0 @@ -" A few useful sets of abbreviations - -" A few things that are hard to write in ASCII -abbreviate (R) © -abbreviate (TM) â„¢ 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/numbertoggle.lua b/home/vim/plugin/numbertoggle.lua new file mode 100644 index 0000000..1f97fc8 --- /dev/null +++ b/home/vim/plugin/numbertoggle.lua @@ -0,0 +1,23 @@ +-- Show lines numbers +vim.opt.number = true + +local numbertoggle = vim.api.nvim_create_augroup("numbertoggle", { clear = true }) + +-- Toggle numbers between relative and absolute when changing buffers +vim.api.nvim_create_autocmd({ "BufEnter", "FocusGained", "InsertLeave", "WinEnter" }, { + pattern = "*", + group = numbertoggle, + command = "if &nu | setlocal rnu | endif", +}) +vim.api.nvim_create_autocmd({ "BufLeave", "FocusLost", "InsertEnter", "WinLeave" }, { + pattern = "*", + group = numbertoggle, + command = "if &nu | setlocal nornu | endif", +}) + +-- Never show the sign column in a terminal buffer +vim.api.nvim_create_autocmd({ "TermOpen" }, { + pattern = "*", + group = numbertoggle, + command = "setlocal nonu nornu", +}) diff --git a/home/vim/plugin/numbertoggle.vim b/home/vim/plugin/numbertoggle.vim deleted file mode 100644 index d9a969d..0000000 --- a/home/vim/plugin/numbertoggle.vim +++ /dev/null @@ -1,13 +0,0 @@ -" Idea for toggling taken from jeffkreeftmeijer - -" Show line numbers -set number - -augroup numbertoggle - autocmd! - " Toggle numbers between relative and absolute when changing buffers - autocmd BufEnter,FocusGained,InsertLeave,WinEnter * if &nu | set rnu | endif - autocmd BufLeave,FocusLost,InsertEnter,WinLeave * if &nu | set nornu | endif - " Disable line numbers and relative line numbers in terminal - autocmd TermOpen * setlocal nonu nornu -augroup END 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.lua b/home/vim/plugin/settings/completion.lua new file mode 100644 index 0000000..2d150e8 --- /dev/null +++ b/home/vim/plugin/settings/completion.lua @@ -0,0 +1,62 @@ +-- Show completion menu in all cases, and don't select anything +vim.opt.completeopt = { "menu", "menuone", "noselect" } + +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, + }, +}) diff --git a/home/vim/plugin/settings/dressing.lua b/home/vim/plugin/settings/dressing.lua new file mode 100644 index 0000000..3928a59 --- /dev/null +++ b/home/vim/plugin/settings/dressing.lua @@ -0,0 +1,6 @@ +local dressing = require("dressing") + +dressing.setup({ + -- Use a relative prompt size + prefer_width = 0.4, +}) diff --git a/home/vim/plugin/settings/fastfold.lua b/home/vim/plugin/settings/fastfold.lua new file mode 100644 index 0000000..78ee937 --- /dev/null +++ b/home/vim/plugin/settings/fastfold.lua @@ -0,0 +1,5 @@ +-- Intercept all fold commands +-- stylua: ignore +vim.g.fastfold_fold_command_suffixes = { + "x", "X", "a", "A", "o", "O", "c", "C", "r", "R", "m", "M", "i", "n", "N", +} diff --git a/home/vim/plugin/settings/fastfold.vim b/home/vim/plugin/settings/fastfold.vim deleted file mode 100644 index a1f1787..0000000 --- a/home/vim/plugin/settings/fastfold.vim +++ /dev/null @@ -1,5 +0,0 @@ -" Intercept all fold commands -let g:fastfold_fold_command_suffixes=[ - \ 'x', 'X', 'a', 'A', 'o', 'O', 'c', 'C', - \ 'r', 'R', 'm', 'M', 'i', 'n', 'N' - \ ] diff --git a/home/vim/plugin/settings/formatting.lua b/home/vim/plugin/settings/formatting.lua new file mode 100644 index 0000000..1bb896b --- /dev/null +++ b/home/vim/plugin/settings/formatting.lua @@ -0,0 +1,3 @@ +local lsp_format = require("lsp-format") + +lsp_format.setup({}) 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.lua b/home/vim/plugin/settings/git.lua new file mode 100644 index 0000000..4dbebca --- /dev/null +++ b/home/vim/plugin/settings/git.lua @@ -0,0 +1,58 @@ +local gitsigns = require("gitsigns") +local wk = require("which-key") + +gitsigns.setup({ + 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" }, + -- stylua: ignore + 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" }) 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/lsp-lines.lua b/home/vim/plugin/settings/lsp-lines.lua new file mode 100644 index 0000000..9c79818 --- /dev/null +++ b/home/vim/plugin/settings/lsp-lines.lua @@ -0,0 +1,3 @@ +local lsp_lines = require("lsp_lines") + +lsp_lines.setup() diff --git a/home/vim/plugin/settings/lspconfig.lua b/home/vim/plugin/settings/lspconfig.lua new file mode 100644 index 0000000..794a765 --- /dev/null +++ b/home/vim/plugin/settings/lspconfig.lua @@ -0,0 +1,61 @@ +local lspconfig = require("lspconfig") +local lsp = require("ambroisie.lsp") +local utils = require("ambroisie.utils") + +-- Diagnostics +vim.diagnostic.config({ + -- Disable virtual test next to affected regions + virtual_text = false, + -- Also disable virtual diagnostics under the affected regions + virtual_lines = 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, +}) + +-- Inform servers we are able to do completion, snippets, etc... +local capabilities = require("cmp_nvim_lsp").default_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("nil") then + lspconfig.nil_ls.setup({ + capabilities = capabilities, + on_attach = lsp.on_attach, + }) +end + +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 diff --git a/home/vim/plugin/settings/lualine.lua b/home/vim/plugin/settings/lualine.lua new file mode 100644 index 0000000..fdaccda --- /dev/null +++ b/home/vim/plugin/settings/lualine.lua @@ -0,0 +1,61 @@ +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", + }, +}) diff --git a/home/vim/plugin/settings/luasnip.lua b/home/vim/plugin/settings/luasnip.lua new file mode 100644 index 0000000..80309d7 --- /dev/null +++ b/home/vim/plugin/settings/luasnip.lua @@ -0,0 +1 @@ +require("luasnip.loaders.from_vscode").lazy_load() diff --git a/home/vim/plugin/settings/notify.lua b/home/vim/plugin/settings/notify.lua new file mode 100644 index 0000000..b68c90f --- /dev/null +++ b/home/vim/plugin/settings/notify.lua @@ -0,0 +1,14 @@ +local notify = require("notify") + +notify.setup({ + icons = { + DEBUG = "D", + ERROR = "E", + INFO = "I", + TRACE = "T", + WARN = "W", + }, + stages = "slide", +}) + +vim.notify = notify diff --git a/home/vim/plugin/settings/null-ls.lua b/home/vim/plugin/settings/null-ls.lua new file mode 100644 index 0000000..0eaa55c --- /dev/null +++ b/home/vim/plugin/settings/null-ls.lua @@ -0,0 +1,138 @@ +local null_ls = require("null-ls") +local lsp = require("ambroisie.lsp") +local utils = require("ambroisie.utils") + +null_ls.setup({ + on_attach = lsp.on_attach, +}) + +-- Bazel +null_ls.register({ + null_ls.builtins.diagnostics.buildifier.with({ + -- Only used if available + condition = utils.is_executable_condition("buildifier"), + }), + null_ls.builtins.formatting.buildifier.with({ + -- Only used if available + condition = utils.is_executable_condition("buildifier"), + }), +}) + +-- 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") + and not utils.is_executable("nil") + 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" }, + -- Restrict to POSIX sh + filetypes = { "sh" }, + -- Only used if available + condition = utils.is_executable_condition("shfmt"), + }), +}) diff --git a/home/vim/plugin/settings/pandoc.vim b/home/vim/plugin/settings/pandoc.vim deleted file mode 100644 index 71b750c..0000000 --- a/home/vim/plugin/settings/pandoc.vim +++ /dev/null @@ -1,20 +0,0 @@ -" Which code-block languages should I expect to be high-lighted. -let g:pandoc#syntax#codeblocks#embeds#langs=[ - \ "bash=sh", - \ "c", - \ "cpp", - \ "go", - \ "haskell", - \ "python", - \ "rust", - \ "sh", - \ "vim", - \ "yaml", - \ "tex", - \ "toml", - \ "perl", - \ "json", - \ "latex=tex", - \ "make", - \ "makefile=make", - \ ] diff --git a/home/vim/plugin/settings/ssh.lua b/home/vim/plugin/settings/ssh.lua new file mode 100644 index 0000000..992a707 --- /dev/null +++ b/home/vim/plugin/settings/ssh.lua @@ -0,0 +1,17 @@ +if not require("ambroisie.utils").is_ssh() then + return +end + +local function copy(lines, _) + require("osc52").copy(table.concat(lines, "\n")) +end + +local function paste() + return { vim.fn.split(vim.fn.getreg(""), "\n"), vim.fn.getregtype("") } +end + +vim.g.clipboard = { + name = "osc52", + copy = { ["+"] = copy, ["*"] = copy }, + paste = { ["+"] = paste, ["*"] = paste }, +} diff --git a/home/vim/plugin/settings/surround.lua b/home/vim/plugin/settings/surround.lua new file mode 100644 index 0000000..3585a12 --- /dev/null +++ b/home/vim/plugin/settings/surround.lua @@ -0,0 +1,3 @@ +require("nvim-surround").setup({ + -- No configuration at the moment +}) diff --git a/home/vim/plugin/settings/telescope.lua b/home/vim/plugin/settings/telescope.lua new file mode 100644 index 0000000..ed8ebc7 --- /dev/null +++ b/home/vim/plugin/settings/telescope.lua @@ -0,0 +1,25 @@ +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") +telescope.load_extension("notify") diff --git a/home/vim/plugin/settings/tree-sitter.lua b/home/vim/plugin/settings/tree-sitter.lua new file mode 100644 index 0000000..0d84abd --- /dev/null +++ b/home/vim/plugin/settings/tree-sitter.lua @@ -0,0 +1,56 @@ +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", + }, + }, + }, +}) diff --git a/home/vim/plugin/settings/which-key.lua b/home/vim/plugin/settings/which-key.lua new file mode 100644 index 0000000..2edfd70 --- /dev/null +++ b/home/vim/plugin/settings/which-key.lua @@ -0,0 +1,2 @@ +local wk = require("which-key") +wk.setup() diff --git a/home/vim/plugin/signtoggle.lua b/home/vim/plugin/signtoggle.lua new file mode 100644 index 0000000..d6a26e2 --- /dev/null +++ b/home/vim/plugin/signtoggle.lua @@ -0,0 +1,20 @@ +local signtoggle = vim.api.nvim_create_augroup("signtoggle", { clear = true }) + +-- Only show sign column for the currently focused buffer +vim.api.nvim_create_autocmd({ "BufEnter", "FocusGained", "WinEnter" }, { + pattern = "*", + group = signtoggle, + command = "setlocal signcolumn=yes", +}) +vim.api.nvim_create_autocmd({ "BufLeave", "FocusLost", "WinLeave" }, { + pattern = "*", + group = signtoggle, + command = "setlocal signcolumn=yes", +}) + +-- Never show the sign column in a terminal buffer +vim.api.nvim_create_autocmd({ "TermOpen" }, { + pattern = "*", + group = signtoggle, + command = "setlocal signcolumn=no", +}) diff --git a/home/vim/plugin/signtoggle.vim b/home/vim/plugin/signtoggle.vim deleted file mode 100644 index c2f0183..0000000 --- a/home/vim/plugin/signtoggle.vim +++ /dev/null @@ -1,8 +0,0 @@ -augroup signtoggle - autocmd! - " Only show the sign column for the current focused buffer - autocmd BufEnter,FocusGained,WinEnter * set signcolumn=yes - autocmd BufLeave,FocusLost,WinLeave * set signcolumn=no - " Disable the sign column in terminal - autocmd TermOpen * setlocal signcolumn=no -augroup END 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..6a615e5 --- /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 = "${lib.getExe pkgs.i3lock} -n -c 000000"; + example = "\${lib.getExe pkgs.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..d1dbe2c --- /dev/null +++ b/home/wm/i3/default.nix @@ -0,0 +1,389 @@ +{ 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 = lib.getExe pkgs.libnotify; + + # Screen backlight management + changeBacklight = lib.getExe pkgs.ambroisie.change-backlight; + + # Audio and volume management + changeAudio = lib.getExe pkgs.ambroisie.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 + playerctl # Used by a mapping + xdotool # Used by 'rofi-rbw', in a mapping + ]; + + xsession.windowManager.i3 = { + enable = true; + + config = { + inherit modifier; + + bars = + let + i3status-rs = lib.getExe config.programs.i3status-rust.package; + in + assert [ "top" ] == lib.attrNames config.programs.i3status-rust.bars; + [ + { + statusCommand = "${i3status-rs} config-top.toml"; + 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"; + # Center floating window + "${modifier}+c" = "move absolute position center"; + # Center floating window + "${modifier}+Shift+s" = "sticky 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}+Ctrl+p" = "exec ${lib.getExe pkgs.rofi-rbw}"; + "${modifier}+Shift+p" = "exec rofi -show emoji"; + "${modifier}+b" = + let + inherit (config.my.home.bluetooth) enable; + prog = lib.getExe pkgs.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..05b0f50 --- /dev/null +++ b/home/wm/i3bar/default.nix @@ -0,0 +1,114 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.home.wm.i3bar; +in +{ + config = lib.mkIf cfg.enable { + home.packages = with pkgs; [ + alsa-utils # 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"; + # This format seems to remove the block when not playing, somehow + format = "{ $icon $combo.str(max_w:50,rot_interval:0.5) $prev $play $next |}"; + click = [ + { + button = "play"; + action = "music_play"; + } + { + button = "prev"; + action = "music_prev"; + } + { + button = "next"; + action = "music_next"; + } + ]; + } + (lib.optionalAttrs config.my.home.bluetooth.enable { + block = "bluetooth"; + mac = "4C:87:5D:06:40:D9"; + format = " $icon Boson{ $percentage|} "; + disconnected_format = ""; + }) + (lib.optionalAttrs config.my.home.bluetooth.enable { + block = "bluetooth"; + mac = "38:18:4C:BE:8E:97"; + format = " $icon Muon{ $percentage|} "; + disconnected_format = ""; + }) + (lib.optionalAttrs config.my.home.bluetooth.enable { + block = "bluetooth"; + mac = "94:DB:56:00:EE:93"; + format = " $icon Protons{ $percentage|} "; + disconnected_format = ""; + }) + (lib.optionalAttrs config.my.home.bluetooth.enable { + block = "bluetooth"; + mac = "88:C9:E8:6B:B7:55"; + format = " $icon Quarks{ $percentage|} "; + disconnected_format = ""; + }) + (lib.optionalAttrs config.my.home.bluetooth.enable { + block = "bluetooth"; + mac = "F7:78:BA:76:52:F7"; + format = " $icon MX Ergo{ $percentage|} "; + disconnected_format = ""; + }) + { + block = "cpu"; + } + { + block = "disk_space"; + } + { + block = "net"; + format = " $icon{| $ssid|} $ip{| $signal_strength|} "; + } + { + block = "backlight"; + invert_icons = true; + } + { + block = "battery"; + format = " $icon $percentage{ ($time)|} "; + empty_format = " $icon $percentage{ ($time)|} "; + not_charging_format = " $icon $percentage "; + full_format = " $icon $percentage "; + } + { + block = "temperature"; + format_alt = " $icon "; + } + { + block = "sound"; + device_kind = "source"; # Microphone status + format = " $icon "; + } + { + block = "sound"; + show_volume_when_muted = true; + } + { + block = "time"; + format = " $icon $timestamp.datetime(f:'%F %T') "; + interval = 5; + } + ]; + }; + }; + }; + }; +} 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..3b2ead6 --- /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 = "${lib.getExe pkgs.libnotify} -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 75% rename from home/xdg.nix rename to home/xdg/default.nix index bdeb326..3fd8dc9 100644 --- a/home/xdg.nix +++ b/home/xdg/default.nix @@ -3,8 +3,8 @@ let cfg = config.my.home.xdg; in { - options.my.home.xdg = with lib.my; { - enable = mkDisableOption "XDG configuration"; + options.my.home.xdg = with lib; { + enable = my.mkDisableOption "XDG configuration"; }; config.xdg = lib.mkIf cfg.enable { @@ -31,18 +31,23 @@ in # A tidy home is a tidy mind dataFile = { "bash/.keep".text = ""; + "gdb/.keep".text = ""; "tig/.keep".text = ""; }; }; # I want a tidier home config.home.sessionVariables = with config.xdg; lib.mkIf cfg.enable { + ANDROID_HOME = "${dataHome}/android"; 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"; + PSQL_HISTORY = "${dataHome}/psql_history"; + REDISCLI_HISTFILE = "${dataHome}/redis/rediscli_history"; + XCOMPOSECACHE = "${dataHome}/X11/xcompose"; }; } 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/completion-styles.zsh b/home/zsh/completion-styles.zsh index a0181a5..156bc2c 100644 --- a/home/zsh/completion-styles.zsh +++ b/home/zsh/completion-styles.zsh @@ -8,6 +8,8 @@ zstyle ':completion:*' menu select zstyle ':completion:*' group-name '' # Keep directories and files separated zstyle ':completion:*' list-dirs-first true +# Expand '//' to '/' +zstyle ':completion:*' squeeze-slashes true # Add colors to processes for kill completion zstyle ':completion:*:*:kill:*:processes' list-colors '=(#b) #([0-9]#)*=0=01;31' diff --git a/home/zsh/default.nix b/home/zsh/default.nix index b7b9e8e..4cadb57 100644 --- a/home/zsh/default.nix +++ b/home/zsh/default.nix @@ -1,79 +1,95 @@ { config, pkgs, lib, ... }: let cfg = config.my.home.zsh; + + # Have a nice relative path for XDG_CONFIG_HOME, without leading `/` + relativeXdgConfig = + let + noHome = lib.removePrefix config.home.homeDirectory; + noSlash = lib.removePrefix "/"; + in + noSlash (noHome config.xdg.configHome); in { - options.my.home.zsh = with lib.my; { - enable = mkDisableOption "zsh configuration"; + options.my.home.zsh = with lib; { + enable = my.mkDisableOption "zsh configuration"; + + launchTmux = mkEnableOption "auto launch tmux at shell start"; }; - 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 = "${relativeXdgConfig}/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 = true; + expireDuplicatesFirst = true; + 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 = [ + { + 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 cfg.launchTmux '' + # Launch tmux unless already inside one + if [ -z "$TMUX" ]; then + exec tmux new-session + fi + '' + } + ''; + + initExtra = '' + source ${./completion-styles.zsh} + source ${./extra-mappings.zsh} + source ${./options.zsh} + + # Source local configuration + if [ -f "$ZDOTDIR/zshrc.local" ]; then + source "$ZDOTDIR/zshrc.local" + fi + ''; + + 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; + }; + + # Enable VTE integration + enableVteIntegration = true; }; - - shellAliases = { - # Sometime `gpg-agent` errors out... - reset-agent = "gpg-connect-agent updatestartuptty /bye"; - }; - }; - - # Fuzzy-wuzzy - config.programs.fzf = lib.mkIf cfg.enable { - enable = true; - enableZshIntegration = true; - }; - - config.programs.dircolors = lib.mkIf cfg.enable { - enable = true; }; } diff --git a/home/zsh/extra-mappings.zsh b/home/zsh/extra-mappings.zsh index abd6e58..3456e13 100644 --- a/home/zsh/extra-mappings.zsh +++ b/home/zsh/extra-mappings.zsh @@ -1,8 +1,7 @@ -# Fix delete key not working -bindkey "\e[3~" delete-char +# shellcheck disable=2154 # Fix Ctrl+u killing from the cursor instead of the whole line -bindkey \^U backward-kill-line +bindkey '^u' backward-kill-line # Use Ctrl+x-(Ctrl+)e to edit the current command line in VISUAL/EDITOR autoload -U edit-command-line @@ -10,5 +9,124 @@ zle -N edit-command-line bindkey '^xe' edit-command-line bindkey '^x^e' edit-command-line +# The expression: (( ${+terminfo} )) should never fail, but does if we +# don't have a tty, perhaps due to a bug in the zsh/terminfo module. +if ! { [ "$TERM" != emacs ] && (( ${+terminfo} )) 2>/dev/null; }; then + return +fi + +# Make sure that the terminal is in application mode when zle is active, since +# only then values from $terminfo are valid +if (( ${+terminfo[smkx]} )) && (( ${+terminfo[rmkx]} )); then + autoload -Uz add-zle-hook-widget + + zle_application_mode_start() { echoti smkx; } + zle_application_mode_stop() { echoti rmkx; } + + add-zle-hook-widget -Uz zle-line-init zle_application_mode_start + add-zle-hook-widget -Uz zle-line-finish zle_application_mode_stop +fi + + +# Fix delete key not working +if [ -n "${terminfo[kdch1]}" ]; then + bindkey -M emacs "${terminfo[kdch1]}" delete-char + bindkey -M viins "${terminfo[kdch1]}" delete-char + bindkey -M vicmd "${terminfo[kdch1]}" delete-char +else + bindkey -M emacs "^[[3~" delete-char + bindkey -M viins "^[[3~" delete-char + bindkey -M vicmd "^[[3~" delete-char + + bindkey -M emacs "^[3;5~" delete-char + bindkey -M viins "^[3;5~" delete-char + bindkey -M vicmd "^[3;5~" delete-char +fi + +# Ctrl-Delete to delete a whole word forward +if [ -n "${terminfo[kdl1]}" ]; then + bindkey -M emacs "${terminfo[kdl1]}" kill-word + bindkey -M viins "${terminfo[kdl1]}" kill-word + bindkey -M vicmd "${terminfo[kdl1]}" kill-word +else + bindkey -M emacs '^[[3;5~' kill-word + bindkey -M viins '^[[3;5~' kill-word + bindkey -M vicmd '^[[3;5~' kill-word +fi + # Enable Shift-Tab to go backwards in completion list -bindkey '^[[Z' reverse-menu-complete +if [ -n "${terminfo[kcbt]}" ]; then + bindkey -M emacs "${terminfo[kcbt]}" reverse-menu-complete + bindkey -M viins "${terminfo[kcbt]}" reverse-menu-complete + bindkey -M vicmd "${terminfo[kcbt]}" reverse-menu-complete +else + bindkey -M emacs '^[[Z' reverse-menu-complete + bindkey -M viins '^[[Z' reverse-menu-complete + bindkey -M vicmd '^[[Z' reverse-menu-complete +fi + +# Ctrl-Left moves backward one word +if [ -n "${terminfo[kLFT5]}" ]; then + bindkey -M emacs "${terminfo[kLFT5]}" backward-word + bindkey -M viins "${terminfo[kLFT5]}" backward-word + bindkey -M vicmd "${terminfo[kLFT5]}" backward-word +else + bindkey -M emacs '^[[1;5D' backward-word + bindkey -M viins '^[[1;5D' backward-word + bindkey -M vicmd '^[[1;5D' backward-word +fi + +# Ctrl-Right moves forward one word +if [ -n "${terminfo[kRIT5]}" ]; then + bindkey -M emacs "${terminfo[kRIT5]}" forward-word + bindkey -M viins "${terminfo[kRIT5]}" forward-word + bindkey -M vicmd "${terminfo[kRIT5]}" forward-word +else + bindkey -M emacs '^[[1;5C' forward-word + bindkey -M viins '^[[1;5C' forward-word + bindkey -M vicmd '^[[1;5C' forward-word +fi + +# PageUp goes backwards in history +if [ -n "${terminfo[kpp]}" ]; then + bindkey -M emacs "${terminfo[kpp]}" up-line-or-history + bindkey -M viins "${terminfo[kpp]}" up-line-or-history + bindkey -M vicmd "${terminfo[kpp]}" up-line-or-history +else + bindkey -M emacs "^[[5~" up-line-or-history + bindkey -M viins "^[[5~" up-line-or-history + bindkey -M vicmd "^[[5~" up-line-or-history +fi + +# PageDown goes forward in history +if [ -n "${terminfo[knp]}" ]; then + bindkey -M emacs "${terminfo[knp]}" down-line-or-history + bindkey -M viins "${terminfo[knp]}" down-line-or-history + bindkey -M vicmd "${terminfo[knp]}" down-line-or-history +else + bindkey -M emacs "^[[6~" down-line-or-history + bindkey -M viins "^[[6~" down-line-or-history + bindkey -M vicmd "^[[6~" down-line-or-history +fi + +# Home goes to the beginning of the line +if [ -n "${terminfo[khome]}" ]; then + bindkey -M emacs "${terminfo[khome]}" beginning-of-line + bindkey -M viins "${terminfo[khome]}" beginning-of-line + bindkey -M vicmd "${terminfo[khome]}" beginning-of-line +else + bindkey -M emacs "^[[1~" beginning-of-line + bindkey -M viins "^[[1~" beginning-of-line + bindkey -M vicmd "^[[1~" beginning-of-line +fi + +# End goes to the end of the line +if [ -n "${terminfo[kend]}" ]; then + bindkey -M emacs "${terminfo[kend]}" end-of-line + bindkey -M viins "${terminfo[kend]}" end-of-line + bindkey -M vicmd "${terminfo[kend]}" end-of-line +else + bindkey -M emacs "^[[4~" end-of-line + bindkey -M viins "^[[4~" end-of-line + bindkey -M vicmd "^[[4~" end-of-line +fi diff --git a/home/zsh/options.zsh b/home/zsh/options.zsh index b02ca54..82047ff 100644 --- a/home/zsh/options.zsh +++ b/home/zsh/options.zsh @@ -1,12 +1,18 @@ # Show an error when a globbing expansion doesn't find any match setopt nomatch # List on ambiguous completion and Insert first match immediately -setopt autolist menucomplete +setopt auto_list menu_complete # Use pushd when cd-ing around -setopt autopushd pushdminus pushdsilent +setopt auto_pushd pushd_minus pushd_silent # Use single quotes in string without the weird escape tricks -setopt rcquotes +setopt rc_quotes # Single word commands can resume an existing job -setopt autoresume +setopt auto_resume +# Show history expansion before running a command +setopt hist_verify +# 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 +unsetopt beep extended_glob notify diff --git a/hosts/homes/ambroisie/default.nix b/hosts/homes/ambroisie/default.nix new file mode 100644 index 0000000..42ea5b8 --- /dev/null +++ b/hosts/homes/ambroisie/default.nix @@ -0,0 +1,5 @@ +# Default home-manager configuration +{ ... }: +{ + # Default configuration, nothing to do +} diff --git a/hosts/homes/ambroisie@ambroisie/default.nix b/hosts/homes/ambroisie@ambroisie/default.nix new file mode 100644 index 0000000..94efc09 --- /dev/null +++ b/hosts/homes/ambroisie@ambroisie/default.nix @@ -0,0 +1,17 @@ +# Google Cloudtop configuration +{ lib, pkgs, ... }: +{ + # Google specific configuration + home.homeDirectory = "/usr/local/google/home/ambroisie"; + + # Some tooling (e.g: SSH) need to use this library + home.sessionVariables = { + LD_PRELOAD = "/lib/x86_64-linux-gnu/libnss_cache.so.2\${LD_PRELOAD:+:}$LD_PRELOAD"; + }; + + systemd.user.sessionVariables = { + LD_PRELOAD = "/lib/x86_64-linux-gnu/libnss_cache.so.2\${LD_PRELOAD:+:}$LD_PRELOAD"; + }; + + programs.git.package = lib.mkForce pkgs.emptyDirectory; +} diff --git a/hosts/nixos/aramis/boot.nix b/hosts/nixos/aramis/boot.nix new file mode 100644 index 0000000..2169da5 --- /dev/null +++ b/hosts/nixos/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/hosts/nixos/aramis/default.nix b/hosts/nixos/aramis/default.nix new file mode 100644 index 0000000..c72fb11 --- /dev/null +++ b/hosts/nixos/aramis/default.nix @@ -0,0 +1,30 @@ +# 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 = [ + ./boot.nix + ./hardware.nix + ./home.nix + ./networking.nix + ./profiles.nix + ./programs.nix + ./secrets + ./services.nix + ./sound.nix + ]; + + # Set your time zone. + time.timeZone = "Europe/London"; + + # 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/hosts/nixos/aramis/hardware.nix b/hosts/nixos/aramis/hardware.nix new file mode 100644 index 0000000..c66b426 --- /dev/null +++ b/hosts/nixos/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/hosts/nixos/aramis/home.nix b/hosts/nixos/aramis/home.nix new file mode 100644 index 0000000..66a0892 --- /dev/null +++ b/hosts/nixos/aramis/home.nix @@ -0,0 +1,34 @@ +{ pkgs, ... }: +{ + my.home = { + # Use graphical pinentry + bitwarden.pinentry = "gtk2"; + # Ebook library + calibre.enable = true; + # 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 + transgui # Transmission remote + ]; + # Minimal video player + mpv.enable = true; + # Network-Manager applet + nm-applet.enable = true; + # Terminal + terminal.program = "alacritty"; + # Zathura document viewer + zathura.enable = true; + }; +} diff --git a/hosts/nixos/aramis/install.sh b/hosts/nixos/aramis/install.sh new file mode 100755 index 0000000..b03a6df --- /dev/null +++ b/hosts/nixos/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/hosts/nixos/aramis/networking.nix b/hosts/nixos/aramis/networking.nix new file mode 100644 index 0000000..fbf4c6b --- /dev/null +++ b/hosts/nixos/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/hosts/nixos/aramis/profiles.nix b/hosts/nixos/aramis/profiles.nix new file mode 100644 index 0000000..d86da5a --- /dev/null +++ b/hosts/nixos/aramis/profiles.nix @@ -0,0 +1,19 @@ +{ ... }: +{ + 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; + # Printers are hell, but so is the unability to print + printing.enable = true; + # i3 configuration + wm.windowManager = "i3"; + # X configuration + x.enable = true; + }; +} diff --git a/hosts/nixos/aramis/programs.nix b/hosts/nixos/aramis/programs.nix new file mode 100644 index 0000000..426ca2a --- /dev/null +++ b/hosts/nixos/aramis/programs.nix @@ -0,0 +1,7 @@ +{ ... }: +{ + my.programs = { + # Steam configuration + steam.enable = true; + }; +} diff --git a/hosts/nixos/aramis/secrets/default.nix b/hosts/nixos/aramis/secrets/default.nix new file mode 100644 index 0000000..c6f02dc --- /dev/null +++ b/hosts/nixos/aramis/secrets/default.nix @@ -0,0 +1,25 @@ +{ config, lib, ... }: + +{ + 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 = [ + # Due to being a laptop, this host does not itself have any SSH keys + "/home/ambroisie/.ssh/agenix" + ]; + }; +} diff --git a/hosts/nixos/aramis/secrets/secrets.nix b/hosts/nixos/aramis/secrets/secrets.nix new file mode 100644 index 0000000..ce159a5 --- /dev/null +++ b/hosts/nixos/aramis/secrets/secrets.nix @@ -0,0 +1,13 @@ +# Host-specific secrets +let + keys = import ../../../../keys; + + all = [ + # This host is a laptop, it does not have a host key + # Allow me to modify the secrets anywhere + keys.users.ambroisie + ]; +in +{ + "wireguard/private-key.age".publicKeys = all; +} diff --git a/hosts/nixos/aramis/secrets/wireguard/private-key.age b/hosts/nixos/aramis/secrets/wireguard/private-key.age new file mode 100644 index 0000000..d790b64 Binary files /dev/null and b/hosts/nixos/aramis/secrets/wireguard/private-key.age differ diff --git a/hosts/nixos/aramis/services.nix b/hosts/nixos/aramis/services.nix new file mode 100644 index 0000000..0287c30 --- /dev/null +++ b/hosts/nixos/aramis/services.nix @@ -0,0 +1,8 @@ +{ ... }: +{ + config.my.services = { + wireguard = { + enable = true; + }; + }; +} diff --git a/hosts/nixos/aramis/sound.nix b/hosts/nixos/aramis/sound.nix new file mode 100644 index 0000000..41ff7f7 --- /dev/null +++ b/hosts/nixos/aramis/sound.nix @@ -0,0 +1,8 @@ +{ ... }: +{ + my.hardware.sound = { + pipewire = { + enable = true; + }; + }; +} diff --git a/machines/porthos/boot.nix b/hosts/nixos/porthos/boot.nix similarity index 84% rename from machines/porthos/boot.nix rename to hosts/nixos/porthos/boot.nix index 3b56eb9..fbc5db7 100644 --- a/machines/porthos/boot.nix +++ b/hosts/nixos/porthos/boot.nix @@ -6,9 +6,8 @@ # Use the GRUB 2 boot loader. loader.grub = { enable = true; - version = 2; # Define on which hard drive you want to install Grub. - device = "/dev/sda"; + device = "/dev/disk/by-id/ata-HGST_HUS724020ALA640_PN2181P6J58M1P"; }; initrd = { diff --git a/porthos.nix b/hosts/nixos/porthos/default.nix similarity index 73% rename from porthos.nix rename to hosts/nixos/porthos/default.nix index ce3a200..326d1cd 100644 --- a/porthos.nix +++ b/hosts/nixos/porthos/default.nix @@ -1,18 +1,20 @@ -# Porthos self-hosted server +# Porthos specific settings { ... }: { imports = [ - # Include generic settings - ./modules - # Include porthos-specific modules - ./machines/porthos - # Include my secrets + ./boot.nix + ./hardware.nix + ./home.nix + ./networking.nix ./secrets - # Include my services - ./services + ./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 diff --git a/machines/porthos/hardware.nix b/hosts/nixos/porthos/hardware.nix similarity index 100% rename from machines/porthos/hardware.nix rename to hosts/nixos/porthos/hardware.nix diff --git a/hosts/nixos/porthos/home.nix b/hosts/nixos/porthos/home.nix new file mode 100644 index 0000000..53d5d25 --- /dev/null +++ b/hosts/nixos/porthos/home.nix @@ -0,0 +1,7 @@ +{ ... }: +{ + my.home = { + # Always start a tmux session when opening a shell session + zsh.launchTmux = true; + }; +} diff --git a/machines/porthos/install.sh b/hosts/nixos/porthos/install.sh similarity index 95% rename from machines/porthos/install.sh rename to hosts/nixos/porthos/install.sh index 44ea787..de87aa7 100644 --- a/machines/porthos/install.sh +++ b/hosts/nixos/porthos/install.sh @@ -46,7 +46,7 @@ vim /mnt/etc/nixos/hardware-configuration.nix mkdir -p /mnt/home/ambroisie/git/nix/config cd /mnt/home/ambroisie/git/nix/config -nix-env -iA nixos.git nixos.nixFlakes nixos.git-crypt +nix-env -iA nixos.git nixos.nix nixos.git-crypt git clone . # Assuming you set up GPG key correctly git crypt unlock diff --git a/machines/porthos/networking.nix b/hosts/nixos/porthos/networking.nix similarity index 94% rename from machines/porthos/networking.nix rename to hosts/nixos/porthos/networking.nix index e593eeb..1e2c9cd 100644 --- a/machines/porthos/networking.nix +++ b/hosts/nixos/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/hosts/nixos/porthos/secrets/acme/dns-key.age b/hosts/nixos/porthos/secrets/acme/dns-key.age new file mode 100644 index 0000000..97d397c --- /dev/null +++ b/hosts/nixos/porthos/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/hosts/nixos/porthos/secrets/backup/password.age b/hosts/nixos/porthos/secrets/backup/password.age new file mode 100644 index 0000000..3af9fbe --- /dev/null +++ b/hosts/nixos/porthos/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/hosts/nixos/porthos/secrets/default.nix b/hosts/nixos/porthos/secrets/default.nix new file mode 100644 index 0000000..83af695 --- /dev/null +++ b/hosts/nixos/porthos/secrets/default.nix @@ -0,0 +1,20 @@ +{ config, lib, ... }: + +{ + 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; + }; +} diff --git a/hosts/nixos/porthos/secrets/drone/gitea.age b/hosts/nixos/porthos/secrets/drone/gitea.age new file mode 100644 index 0000000..90ff83b --- /dev/null +++ b/hosts/nixos/porthos/secrets/drone/gitea.age @@ -0,0 +1,10 @@ +age-encryption.org/v1 +-> ssh-ed25519 jPowng 3HjtINU02yfyvpaoTtCG/G3cgymFvKWf3qf/grw/+24 +G+KnHtrt5A3U1ICTpSceoE+FJSj6hAb4mjp2DkfDWr0 +-> ssh-ed25519 cKojmg CXrsCxJxWNIhvvwYjZ1rrSYTwLNMR3kWdPk5ExHEfFA +OJi3tFcnpdGvxc8ETcGt4lbFJiUU+lR3AqN4Y8PItDk +-> p-grease 7 AQO{DHzj j$B&+ dc +Q7zhy6hOfkEh6XgYNHQrH3zma1BLxnEKDopPAOnBoPFRfi7c +--- ZsL5zf7/jJ6yPor9j1V0iV/bXxgAsxlXsxDYwCqrLSk +AO&âz€?‹;\1¥f*='ë³!.óVí·”VÀÜ=ιªø÷vzÛY"P ²Ê;° ò)¾òK0ëÞBs}hR=öOмç*îMÿ2oœý \ No newline at end of file diff --git a/hosts/nixos/porthos/secrets/drone/secret.age b/hosts/nixos/porthos/secrets/drone/secret.age new file mode 100644 index 0000000..c529200 --- /dev/null +++ b/hosts/nixos/porthos/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/hosts/nixos/porthos/secrets/drone/ssh/private-key.age b/hosts/nixos/porthos/secrets/drone/ssh/private-key.age new file mode 100644 index 0000000..0211701 Binary files /dev/null and b/hosts/nixos/porthos/secrets/drone/ssh/private-key.age differ diff --git a/hosts/nixos/porthos/secrets/gitea/mail-password.age b/hosts/nixos/porthos/secrets/gitea/mail-password.age new file mode 100644 index 0000000..915f8e9 --- /dev/null +++ b/hosts/nixos/porthos/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/hosts/nixos/porthos/secrets/lohr/secret.age b/hosts/nixos/porthos/secrets/lohr/secret.age new file mode 100644 index 0000000..fa310b4 --- /dev/null +++ b/hosts/nixos/porthos/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/hosts/nixos/porthos/secrets/lohr/ssh-key.age b/hosts/nixos/porthos/secrets/lohr/ssh-key.age new file mode 100644 index 0000000..30a5e25 Binary files /dev/null and b/hosts/nixos/porthos/secrets/lohr/ssh-key.age differ diff --git a/hosts/nixos/porthos/secrets/matrix/mail.age b/hosts/nixos/porthos/secrets/matrix/mail.age new file mode 100644 index 0000000..1fe3a71 --- /dev/null +++ b/hosts/nixos/porthos/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/hosts/nixos/porthos/secrets/matrix/secret.age b/hosts/nixos/porthos/secrets/matrix/secret.age new file mode 100644 index 0000000..539c33e Binary files /dev/null and b/hosts/nixos/porthos/secrets/matrix/secret.age differ diff --git a/hosts/nixos/porthos/secrets/miniflux/credentials.age b/hosts/nixos/porthos/secrets/miniflux/credentials.age new file mode 100644 index 0000000..9790159 Binary files /dev/null and b/hosts/nixos/porthos/secrets/miniflux/credentials.age differ diff --git a/hosts/nixos/porthos/secrets/monitoring/password.age b/hosts/nixos/porthos/secrets/monitoring/password.age new file mode 100644 index 0000000..410536f --- /dev/null +++ b/hosts/nixos/porthos/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/hosts/nixos/porthos/secrets/monitoring/secret-key.age b/hosts/nixos/porthos/secrets/monitoring/secret-key.age new file mode 100644 index 0000000..4cef94f Binary files /dev/null and b/hosts/nixos/porthos/secrets/monitoring/secret-key.age differ diff --git a/hosts/nixos/porthos/secrets/nextcloud/password.age b/hosts/nixos/porthos/secrets/nextcloud/password.age new file mode 100644 index 0000000..9fd3c53 Binary files /dev/null and b/hosts/nixos/porthos/secrets/nextcloud/password.age differ diff --git a/hosts/nixos/porthos/secrets/nix-serve/cache-key.age b/hosts/nixos/porthos/secrets/nix-serve/cache-key.age new file mode 100644 index 0000000..e0fb5be Binary files /dev/null and b/hosts/nixos/porthos/secrets/nix-serve/cache-key.age differ diff --git a/hosts/nixos/porthos/secrets/paperless/password.age b/hosts/nixos/porthos/secrets/paperless/password.age new file mode 100644 index 0000000..3fe76cb --- /dev/null +++ b/hosts/nixos/porthos/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/hosts/nixos/porthos/secrets/paperless/secret-key.age b/hosts/nixos/porthos/secrets/paperless/secret-key.age new file mode 100644 index 0000000..eae5c56 --- /dev/null +++ b/hosts/nixos/porthos/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/hosts/nixos/porthos/secrets/podgrab/password.age b/hosts/nixos/porthos/secrets/podgrab/password.age new file mode 100644 index 0000000..90e2501 --- /dev/null +++ b/hosts/nixos/porthos/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/hosts/nixos/porthos/secrets/secrets.nix b/hosts/nixos/porthos/secrets/secrets.nix new file mode 100644 index 0000000..498aebf --- /dev/null +++ b/hosts/nixos/porthos/secrets/secrets.nix @@ -0,0 +1,77 @@ +# Host-specific secrets +let + keys = import ../../../../keys; + + all = [ + # Host key + keys.hosts.porthos + # Allow me to modify the secrets anywhere + keys.users.ambroisie + ]; +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" = { + owner = "matrix-synapse"; + publicKeys = all; + }; + + "miniflux/credentials.age".publicKeys = all; + + "monitoring/password.age" = { + owner = "grafana"; + publicKeys = all; + }; + "monitoring/secret-key.age" = { + owner = "grafana"; + publicKeys = all; + }; + + "nextcloud/password.age" = { + owner = "nextcloud"; + publicKeys = all; + }; + + "nix-serve/cache-key.age".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; + + "tandoor-recipes/secret-key.age".publicKeys = all; + + "transmission/credentials.age".publicKeys = all; + + "vikunja/mail.age".publicKeys = all; + + "wireguard/private-key.age".publicKeys = all; + + "woodpecker/gitea.age".publicKeys = all; + "woodpecker/secret.age".publicKeys = all; + "woodpecker/ssh/private-key.age".publicKeys = all; +} diff --git a/hosts/nixos/porthos/secrets/sso/ambroisie/password-hash.age b/hosts/nixos/porthos/secrets/sso/ambroisie/password-hash.age new file mode 100644 index 0000000..10d9eaa Binary files /dev/null and b/hosts/nixos/porthos/secrets/sso/ambroisie/password-hash.age differ diff --git a/hosts/nixos/porthos/secrets/sso/ambroisie/totp-secret.age b/hosts/nixos/porthos/secrets/sso/ambroisie/totp-secret.age new file mode 100644 index 0000000..c5ce19b Binary files /dev/null and b/hosts/nixos/porthos/secrets/sso/ambroisie/totp-secret.age differ diff --git a/hosts/nixos/porthos/secrets/sso/auth-key.age b/hosts/nixos/porthos/secrets/sso/auth-key.age new file mode 100644 index 0000000..4e05b15 Binary files /dev/null and b/hosts/nixos/porthos/secrets/sso/auth-key.age differ diff --git a/hosts/nixos/porthos/secrets/sso/default.nix b/hosts/nixos/porthos/secrets/sso/default.nix new file mode 100644 index 0000000..e65a55b --- /dev/null +++ b/hosts/nixos/porthos/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/hosts/nixos/porthos/secrets/tandoor-recipes/secret-key.age b/hosts/nixos/porthos/secrets/tandoor-recipes/secret-key.age new file mode 100644 index 0000000..2ec147d Binary files /dev/null and b/hosts/nixos/porthos/secrets/tandoor-recipes/secret-key.age differ diff --git a/hosts/nixos/porthos/secrets/transmission/credentials.age b/hosts/nixos/porthos/secrets/transmission/credentials.age new file mode 100644 index 0000000..4f407fa --- /dev/null +++ b/hosts/nixos/porthos/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/hosts/nixos/porthos/secrets/vikunja/mail.age b/hosts/nixos/porthos/secrets/vikunja/mail.age new file mode 100644 index 0000000..4c83acd Binary files /dev/null and b/hosts/nixos/porthos/secrets/vikunja/mail.age differ diff --git a/hosts/nixos/porthos/secrets/wireguard/private-key.age b/hosts/nixos/porthos/secrets/wireguard/private-key.age new file mode 100644 index 0000000..4abe1e5 --- /dev/null +++ b/hosts/nixos/porthos/secrets/wireguard/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/hosts/nixos/porthos/secrets/woodpecker/gitea.age b/hosts/nixos/porthos/secrets/woodpecker/gitea.age new file mode 100644 index 0000000..e6ede6c Binary files /dev/null and b/hosts/nixos/porthos/secrets/woodpecker/gitea.age differ diff --git a/hosts/nixos/porthos/secrets/woodpecker/secret.age b/hosts/nixos/porthos/secrets/woodpecker/secret.age new file mode 100644 index 0000000..63a4862 --- /dev/null +++ b/hosts/nixos/porthos/secrets/woodpecker/secret.age @@ -0,0 +1,10 @@ +age-encryption.org/v1 +-> ssh-ed25519 jPowng yz0I+AazPmamF7NOnwYNrPE/ArarU01jd2mVDJUPSTY +6Y/YQ7gb8cAZf3zT9SKOorvfUnU7kYff+gHh8fG2mY8 +-> ssh-ed25519 cKojmg 0FZU9v8eHsVeE+EoX9Y4IgfIj/8+45waPaSnSDb961I +L6SzJoh5xqai45scoVAa6v9zslBGFYNnZY044d470uQ +-> I[G-grease p +AMRQY1alSzHi/PLL80kcvnM1Z9YNfoUo9u5alWXYMyzrRsg+vXjMuBvAXg3fmnzr +wdOowTYMRV+jEG8vzkcQTsv+f7JIyo4DvOOaPyGfWMl1 +--- ih3IAFPcN1JP3FP1vcRGnPrfk91yrnIX0m/Szkbcf7Q +ÑmW„r‚µœ_\)Ͱ]QЦxMÃs/݃Îݪäœó‚Í6óº“k±äÅY§xïMy¶ J¿¸‹GßÃ)i2_'ÖœHF€þ.âg_Îe5³#uätñØÕ 7j„ŽPñ²'TÞ¥8´•\IàW«UùäK­°1Úº9½è \ No newline at end of file diff --git a/hosts/nixos/porthos/secrets/woodpecker/ssh/private-key.age b/hosts/nixos/porthos/secrets/woodpecker/ssh/private-key.age new file mode 100644 index 0000000..0211701 Binary files /dev/null and b/hosts/nixos/porthos/secrets/woodpecker/ssh/private-key.age differ diff --git a/hosts/nixos/porthos/services.nix b/hosts/nixos/porthos/services.nix new file mode 100644 index 0000000..e4cae5e --- /dev/null +++ b/hosts/nixos/porthos/services.nix @@ -0,0 +1,165 @@ +# Deployed services +{ config, lib, ... }: +let + secrets = config.age.secrets; +in +{ + # List services that you want to enable: + my.services = { + # Hosts-based adblock using unbound + adblock = { + enable = true; + }; + # Backblaze B2 backup + backup = { + enable = true; + repository = "b2:porthos-backup"; + # Backup every 6 hours + timerConfig = { + OnActiveSec = "6h"; + OnUnitActiveSec = "6h"; + }; + passwordFile = secrets."backup/password".path; + credentialsFile = secrets."backup/credentials".path; + }; + # My blog and related hosts + blog.enable = true; + calibre-web = { + enable = true; + libraryPath = "/data/media/library"; + }; + drone = { + enable = true; + runners = [ "docker" "exec" ]; + secretFile = secrets."drone/gitea".path; + sharedSecretFile = secrets."drone/secret".path; + }; + # Auto-ban spammy bots and incorrect logins + fail2ban = { + enable = true; + }; + # Flood UI for transmission + flood = { + enable = true; + }; + # Gitea forge + gitea = { + enable = true; + mail = { + enable = true; + host = "smtp.migadu.com:465"; + user = lib.my.mkMailAddress "gitea" "belanyi.fr"; + passwordFile = secrets."gitea/mail-password".path; + }; + }; + # Meta-indexers + indexers = { + prowlarr.enable = true; + }; + # Jellyfin media server + jellyfin.enable = true; + # Gitea mirrorig service + lohr = { + enable = true; + sharedSecretFile = secrets."lohr/secret".path; + sshKeyFile = secrets."lohr/ssh-key".path; + }; + # Matrix backend and Element chat front-end + matrix = { + enable = true; + mailConfigFile = secrets."matrix/mail".path; + # Only necessary when doing the initial registration + secretFile = secrets."matrix/secret".path; + }; + miniflux = { + enable = true; + credentialsFiles = secrets."miniflux/credentials".path; + }; + # Various monitoring dashboards + monitoring = { + enable = true; + grafana = { + passwordFile = secrets."monitoring/password".path; + secretKeyFile = secrets."monitoring/secret-key".path; + }; + }; + # FLOSS music streaming server + navidrome = { + enable = true; + musicFolder = "/data/media/music"; + }; + # Nextcloud self-hosted cloud + nextcloud = { + enable = true; + passwordFile = secrets."nextcloud/password".path; + }; + nix-serve = { + enable = true; + secretKeyFile = secrets."nix-serve/cache-key".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 = secrets."podgrab/password".path; + port = 9598; + }; + # Regular backups + postgresql-backup.enable = true; + # RSS provider for websites that do not provide any feeds + rss-bridge.enable = true; + # Usenet client + sabnzbd.enable = true; + # Because I stilll need to play sysadmin + ssh-server.enable = true; + # Recipe manager + tandoor-recipes = { + enable = true; + secretKeyFile = secrets."tandoor-recipes/secret-key".path; + }; + # Torrent client and webui + transmission = { + enable = true; + credentialsFile = secrets."transmission/credentials".path; + }; + # Simple, in-kernel VPN + wireguard = { + enable = true; + startAtBoot = true; # Server must be started to ensure clients can connect + }; + woodpecker = { + enable = true; + # Avoid clashes with drone + port = 3035; + rpcPort = 3036; + runners = [ "docker" "exec" ]; + secretFile = secrets."woodpecker/gitea".path; + sharedSecretFile = secrets."woodpecker/secret".path; + }; + }; +} diff --git a/hosts/nixos/porthos/ssh/drone.pub b/hosts/nixos/porthos/ssh/drone.pub new file mode 100644 index 0000000..d1f971c --- /dev/null +++ b/hosts/nixos/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/hosts/nixos/porthos/users.nix similarity index 64% rename from machines/porthos/users.nix rename to hosts/nixos/porthos/users.nix index 1a26e3c..fbbe368 100644 --- a/machines/porthos/users.nix +++ b/hosts/nixos/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/keys/default.nix b/keys/default.nix new file mode 100644 index 0000000..a538328 --- /dev/null +++ b/keys/default.nix @@ -0,0 +1,39 @@ +# Populate agenix keys from a central location +let + inherit (builtins) + mapAttrs + readDir + readFile + stringLength + substring + ; + + removeSuffix = suffix: str: + let + sufLen = stringLength suffix; + sLen = stringLength str; + in + if sufLen <= sLen && suffix == substring (sLen - sufLen) sufLen str then + substring 0 (sLen - sufLen) str + else + str; + + + readKeys = dir: + let + files = readDir dir; + readNoNewlines = f: removeSuffix "\n" (readFile f); + readKey = name: readNoNewlines (dir + "/${name}"); + in + mapAttrs (n: _: readKey n) files; + + hosts = readKeys ./hosts; + users = readKeys ./users; +in +{ + inherit + hosts + users; + + all = (builtins.attrValues hosts) ++ (builtins.attrValues users); +} diff --git a/keys/hosts/porthos b/keys/hosts/porthos new file mode 100644 index 0000000..7156513 --- /dev/null +++ b/keys/hosts/porthos @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICGzznQ3LSmBYHx6fXthgMDiTcU5i/Nvj020SbmhzAFb root@porthos diff --git a/keys/users/ambroisie b/keys/users/ambroisie new file mode 100644 index 0000000..cf08a3c --- /dev/null +++ b/keys/users/ambroisie @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMIVd6Oh08iUNb1vTULbxGpevnh++wxsWW9wqhaDryIq ambroisie@agenix diff --git a/lib/attrs.nix b/lib/attrs.nix index 4595467..31686ac 100644 --- a/lib/attrs.nix +++ b/lib/attrs.nix @@ -1,7 +1,57 @@ { 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 non-recursively, later values override previous ones. + # + # merge :: + # [ attrs ] + # attrs + merge = foldl (a: b: a // b) { }; + + # 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..894e351 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/ip.nix b/lib/ip.nix index 2af3fef..fcafd72 100644 --- a/lib/ip.nix +++ b/lib/ip.nix @@ -100,7 +100,7 @@ rec { # Pretty print a parsed subnet into a human readable form prettySubnet4 = { baseIp, cidr, ... }: "${prettyIp4 baseIp}/${toString cidr}"; - # Get the nth address from an IPv4 range, without checking if it is in range + # Get the nth address from an IPv4 range nthInRange4 = { from, to }: n: let carry = lhs: { carry, acc }: @@ -112,8 +112,15 @@ rec { acc = [ (mod totVal 256) ] ++ acc; }; carried = foldr carry { carry = n; acc = [ ]; } from; + checkInRange = + if (to - from) < n + then + warn '' + nthInRange4: '${n}'-th address outside of range (${prettyIp4 from}, ${prettyIp4 to}) + '' + else id; in - carried.acc; + checkInRange carried.acc; # Convert an IPv4 range into a list of all its constituent addresses rangeIp4 = 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..a1f74e4 --- /dev/null +++ b/lib/strings.nix @@ -0,0 +1,7 @@ +{ ... }: +{ + # Make an email address from the name and domain stems + # + # mkMailAddress :: String -> String -> String + mkMailAddress = name: domain: "${name}@${domain}"; +} diff --git a/machines/porthos/default.nix b/machines/porthos/default.nix deleted file mode 100644 index d8726f2..0000000 --- a/machines/porthos/default.nix +++ /dev/null @@ -1,12 +0,0 @@ -# Porthos specific settings -{ ... }: - -{ - imports = [ - ./boot.nix - ./hardware.nix - ./networking.nix - ./services.nix - ./users.nix - ]; -} diff --git a/machines/porthos/services.nix b/machines/porthos/services.nix deleted file mode 100644 index 9bc99e7..0000000 --- a/machines/porthos/services.nix +++ /dev/null @@ -1,114 +0,0 @@ -# Deployed services -{ config, ... }: -let - my = config.my; -in -{ - # List services that you want to enable: - my.services = { - # Hosts-based adblock using unbound - adblock = { - enable = true; - }; - # Backblaze B2 backup - backup = { - enable = true; - repository = "b2:porthos-backup"; - # Backup every 6 hours - timerConfig = { - 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; - }; - # My blog and related hosts - blog.enable = true; - calibre-web = { - enable = true; - libraryPath = "/data/media/library"; - }; - 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; - }; - # Gitea forge - gitea.enable = true; - # Meta-indexers - indexers = { - jackett.enable = true; - nzbhydra.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; - }; - # Matrix backend and Element chat front-end - matrix = { - enable = true; - secret = my.secrets.matrix.secret; - }; - miniflux = { - enable = true; - password = my.secrets.miniflux.password; - }; - # Nextcloud self-hosted cloud - nextcloud = { - enable = true; - password = my.secrets.nextcloud.password; - }; - # 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; - port = 9598; - }; - # Regular backups - postgresql-backup.enable = true; - # An IRC client daemon - quassel.enable = true; - # RSS provider for websites that do not provide any feeds - rss-bridge.enable = true; - # Usenet client - sabnzbd.enable = true; - # Because I stilll need to play sysadmin - ssh-server.enable = true; - # Torrent client and webui - transmission = { - enable = true; - username = "Ambroisie"; - password = my.secrets.transmission.password; - }; - # Simple, in-kernel VPN - wireguard = { - enable = true; - startAtBoot = true; # Server must be started to ensure clients can connect - }; - }; - - programs.gnupg.agent = { - enable = true; - enableSSHSupport = true; - }; -} 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..2d840f9 --- /dev/null +++ b/modules/hardware/bluetooth/default.nix @@ -0,0 +1,56 @@ +{ 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; + }; + + environment.etc = { + "wireplumber/bluetooth.lua.d/51-bluez-config.lua".text = '' + bluez_monitor.properties = { + -- SBC XQ provides better audio + ["bluez5.enable-sbc-xq"] = true, + + -- mSBC provides better audio + microphone + ["bluez5.enable-msbc"] = true, + + -- Synchronize volume with bluetooth device + ["bluez5.enable-hw-volume"] = true, + + -- FIXME: Some devices may now support both hsp_ag and hfp_ag + ["bluez5.headset-roles"] = "[ hsp_hs hsp_ag hfp_hf hfp_ag ]" + } + ''; + }; + }) + + # 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..a77135c --- /dev/null +++ b/modules/hardware/firmware/default.nix @@ -0,0 +1,38 @@ +{ config, lib, ... }: +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..1e0e0aa --- /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.nixosModules.home-manager # 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..0c7f9da --- /dev/null +++ b/modules/programs/steam/default.nix @@ -0,0 +1,39 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.programs.steam; + + steam = pkgs.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.writeShellScriptBin "steam" '' + mkdir -p "${cfg.dataDir}" + HOME="${cfg.dataDir}" exec ${lib.getExe steam} "$@" + '') + # Same, for GOG and other such games + (pkgs.writeShellScriptBin "steam-run" '' + mkdir -p "${cfg.dataDir}" + HOME="${cfg.dataDir}" exec ${lib.getExe steam.run} "$@" + '') + ]; + }; +} diff --git a/modules/secrets/default.nix b/modules/secrets/default.nix new file mode 100644 index 0000000..c7d6f65 --- /dev/null +++ b/modules/secrets/default.nix @@ -0,0 +1,24 @@ +{ config, inputs, lib, ... }: + +{ + 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; + }; +} diff --git a/modules/secrets/secrets.nix b/modules/secrets/secrets.nix new file mode 100644 index 0000000..0e685d9 --- /dev/null +++ b/modules/secrets/secrets.nix @@ -0,0 +1,10 @@ +# Common secrets +let + keys = import ../../keys; + + inherit (keys) all; +in +{ + "users/ambroisie/hashed-password.age".publicKeys = all; + "users/root/hashed-password.age".publicKeys = all; +} 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/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..4b646c3 --- /dev/null +++ b/modules/services/blog/default.nix @@ -0,0 +1,46 @@ +# 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"; + + # http://www.gnuterrypratchett.com/ + extraConfig = '' + add_header X-Clacks-Overhead "GNU Terry Pratchett"; + ''; + }; + + # Dummy vhost to redirect all unknown (sub-)domains to my blog + "_" = { + forceSSL = true; + useACMEHost = domain; + default = true; + + locations."/".return = "302 https://belanyi.fr$request_uri"; + }; + }; + + # 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 57% rename from services/calibre-web.nix rename to modules/services/calibre-web/default.nix index d4d7ece..858851c 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,18 +37,37 @@ 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 ]; }; + + services.fail2ban.jails = { + calibre-web = '' + enabled = true + filter = calibre-web + port = http,https + ''; + }; + + environment.etc = { + "fail2ban/filter.d/calibre-web.conf".text = '' + [Definition] + failregex = ^.*Login failed for user ".*" IP-address: $ + journalmatch = _SYSTEMD_UNIT=calibre-web.service + ''; + }; }; } diff --git a/modules/services/default.nix b/modules/services/default.nix new file mode 100644 index 0000000..86badf5 --- /dev/null +++ b/modules/services/default.nix @@ -0,0 +1,40 @@ +{ ... }: + +{ + imports = [ + ./adblock + ./backup + ./blog + ./calibre-web + ./drone + ./fail2ban + ./flood + ./gitea + ./grocy + ./indexers + ./jellyfin + ./lohr + ./matrix + ./miniflux + ./monitoring + ./navidrome + ./nextcloud + ./nginx + ./nix-serve + ./paperless + ./pirate + ./podgrab + ./postgresql + ./postgresql-backup + ./quassel + ./rss-bridge + ./sabnzbd + ./ssh-server + ./tandoor-recipes + ./tlp + ./transmission + ./vikunja + ./wireguard + ./woodpecker + ]; +} 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..e53c608 --- /dev/null +++ b/modules/services/drone/runner-docker/default.nix @@ -0,0 +1,43 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.services.drone; + hasRunner = (name: builtins.elem name cfg.runners); +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 = lib.getExe pkgs.drone-runner-docker; + User = "drone-runner-docker"; + Group = "drone-runner-docker"; + }; + }; + + # Make sure it is activated in that case + my.system.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..a9bb563 --- /dev/null +++ b/modules/services/drone/runner-exec/default.nix @@ -0,0 +1,67 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.services.drone; + hasRunner = (name: builtins.elem name cfg.runners); +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 + nix + gzip + ]; + path = with pkgs; [ + git + gnutar + bash + nix + 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 = lib.getExe pkgs.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/fail2ban/default.nix b/modules/services/fail2ban/default.nix new file mode 100644 index 0000000..d62b9e2 --- /dev/null +++ b/modules/services/fail2ban/default.nix @@ -0,0 +1,37 @@ +# A minimalist, opinionated feed reader +{ config, lib, ... }: +let + cfg = config.my.services.fail2ban; + wgNetCfg = config.my.services.wireguard.net; +in +{ + options.my.services.fail2ban = with lib; { + enable = mkEnableOption "fail2ban daemon"; + }; + + config = lib.mkIf cfg.enable { + services.fail2ban = { + enable = true; + + ignoreIP = [ + # Wireguard IPs + "${wgNetCfg.v4.subnet}.0/${toString wgNetCfg.v4.mask}" + "${wgNetCfg.v6.subnet}::/${toString wgNetCfg.v6.mask}" + # Loopback addresses + "127.0.0.0/8" + ]; + + maxretry = 5; + + bantime-increment = { + enable = true; + rndtime = "5m"; # Use 5 minute jitter to avoid unban evasion + }; + + jails.DEFAULT.settings = { + findtime = "4h"; + bantime = "10m"; + }; + }; + }; +} diff --git a/modules/services/flood/default.nix b/modules/services/flood/default.nix new file mode 100644 index 0000000..ff5d941 --- /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 " " [ + (lib.getExe pkgs.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..28a448d --- /dev/null +++ b/modules/services/gitea/default.nix @@ -0,0 +1,154 @@ +# 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 = "git.${domain}"; + in + { + enable = true; + + appName = "Ambroisie's forge"; + + 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 = { + server = { + HTTP_PORT = cfg.port; + DOMAIN = giteaDomain; + ROOT_URL = "https://${giteaDomain}"; + }; + + 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 = { }; + + my.services.nginx.virtualHosts = [ + # Proxy to Gitea + { + subdomain = "git"; + inherit (cfg) port; + } + # Redirect `gitea.` to actual forge subdomain + { + subdomain = "gitea"; + redirect = config.services.gitea.settings.server.ROOT_URL; + } + ]; + + my.services.backup = { + paths = [ + config.services.gitea.lfs.contentDir + config.services.gitea.repositoryRoot + ]; + }; + + services.fail2ban.jails = { + gitea = '' + enabled = true + filter = gitea + action = iptables-allports + ''; + }; + + environment.etc = { + "fail2ban/filter.d/gitea.conf".text = '' + [Definition] + failregex = ^.*(Failed authentication attempt|invalid credentials|Attempted access of unknown user).* from $ + journalmatch = _SYSTEMD_UNIT=gitea.service + ''; + }; + }; +} 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 58% rename from services/lohr.nix rename to modules/services/lohr/default.nix index 57f4feb..245567c 100644 --- a/services/lohr.nix +++ b/modules/services/lohr/default.nix @@ -4,10 +4,8 @@ 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 +35,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 +56,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"; + "${lib.getExe pkgs.ambroisie.lohr} --config ${configFile}"; + 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/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..c73afed 100644 --- a/services/matrix.nix +++ b/modules/services/matrix/default.nix @@ -7,7 +7,6 @@ # [1]: https://github.com/alarsyo/nixos-config/blob/main/services/matrix.nix { config, lib, pkgs, ... }: -with lib; let cfg = config.my.services.matrix; @@ -18,17 +17,26 @@ in { 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 +49,79 @@ 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; - # 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 = @@ -97,8 +148,8 @@ in }; # same as above, but listening on the federation port - "matrix.${domain}_federation" = rec { - forceSSL = true; + "matrix.${domain}_federation" = { + onlySSL = true; serverName = "matrix.${domain}"; useACMEHost = domain; @@ -112,7 +163,6 @@ in { addr = "0.0.0.0"; port = federationPort.public; ssl = true; } { addr = "[::]"; port = federationPort.public; ssl = true; } ]; - }; "${domain}" = { @@ -142,34 +192,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..829bfe0 --- /dev/null +++ b/modules/services/monitoring/default.nix @@ -0,0 +1,135 @@ +# 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"; + }; + + secretKeyFile = mkOption { + type = types.str; + example = "/var/lib/grafana/secret_key.txt"; + description = "Secret key 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; + + settings = { + server = { + domain = "monitoring.${config.networking.domain}"; + root_url = "https://monitoring.${config.networking.domain}/"; + http_port = cfg.grafana.port; + http_addr = "127.0.0.1"; # Proxied through Nginx + }; + + security = { + admin_user = cfg.grafana.username; + admin_password = "$__file{${cfg.grafana.passwordFile}}"; + secret_key = "$__file{${cfg.grafana.secretKeyFile}}"; + }; + }; + + provision = { + enable = true; + + datasources.settings.datasources = [ + { + name = "Prometheus"; + type = "prometheus"; + url = "http://localhost:${toString cfg.prometheus.port}"; + jsonData = { + timeInterval = cfg.prometheus.scrapeInterval; + }; + } + ]; + + dashboards.settings.providers = [ + { + 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 59% rename from services/nextcloud.nix rename to modules/services/nextcloud/default.nix index d52e32a..1477c13 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,27 +18,38 @@ 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.nextcloud27; + hostName = "nextcloud.${config.networking.domain}"; home = "/var/lib/nextcloud"; maxUploadSize = cfg.maxSize; + enableBrokenCiphersForSSE = false; + configureRedis = true; 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 }; + + notify_push = { + enable = true; + # Allow using the push service without hard-coding my IP in the configuration + bendDomainToLocalhost = true; + }; }; services.postgresql = { @@ -59,17 +68,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..dcaaa0f --- /dev/null +++ b/modules/services/nginx/default.nix @@ -0,0 +1,474 @@ +# A simple abstraction layer for almost all of my services' needs +{ config, lib, pkgs, ... }: +let + cfg = config.my.services.nginx; + + domain = config.networking.domain; + + 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`. + ''; + }; + + redirect = mkOption { + type = with types; nullOr str; + default = null; + example = "https://example.com"; + description = '' + Which domain to redirect to (301 response), for this virtual host. + ''; + }; + + 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`. + ''; + }; + + socket = mkOption { + type = with types; nullOr path; + default = null; + example = "FIXME"; + description = '' + The UNIX socket 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" "socket" "redirect" ]; + 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. + + recommendedBrotliSettings = true; + recommendedGzipSettings = true; + recommendedOptimisation = true; + recommendedProxySettings = true; + recommendedTlsSettings = true; + recommendedZstdSettings = 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; + }) + # Serve to UNIX socket + (lib.optionalAttrs (args.socket != null) { + locations."/".proxyPass = + "http://unix:${args.socket}"; + }) + # Redirect to a different domain + (lib.optionalAttrs (args.redirect != null) { + locations."/".return = "301 ${args.redirect}$request_uri"; + }) + # 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 = + { + "${domain}" = { + extraDomainNames = [ "*.${domain}" ]; + dnsProvider = "gandiv5"; + inherit (cfg.acme) credentialsFile; + }; + }; + }; + + systemd.services."acme-${domain}" = { + serviceConfig = { + Environment = [ + # Since I do a "weird" setup with a wildcard CNAME + "LEGO_DISABLE_CNAME_SUPPORT=true" + ]; + }; + }; + + services.grafana.provision.dashboards.settings.providers = 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..4a78282 --- /dev/null +++ b/modules/services/nginx/sso/default.nix @@ -0,0 +1,89 @@ +# 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.writeShellScript "merge-nginx-sso-config" '' + rm -f '${confPath}' + ${utils.genJqSecretsReplacementSnippet cfg.configuration confPath} + + # Fix permissions + chown nginx-sso:nginx-sso ${confPath} + chmod 0600 ${confPath} + '' + }''; + ExecStart = lib.mkForce '' + ${lib.getExe pkg} \ + --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/nix-serve/default.nix b/modules/services/nix-serve/default.nix new file mode 100644 index 0000000..0cf1573 --- /dev/null +++ b/modules/services/nix-serve/default.nix @@ -0,0 +1,57 @@ +# Binary cache through nix-serve +{ config, lib, pkgs, ... }: +let + cfg = config.my.services.nix-serve; +in +{ + options.my.services.nix-serve = with lib; { + enable = mkEnableOption "nix-serve binary cache"; + + port = mkOption { + type = types.port; + default = 5000; + example = 8080; + description = "Internal port for serving cache"; + }; + + secretKeyFile = mkOption { + type = types.str; + example = "/run/secrets/nix-serve"; + description = "Secret signing key for the cache"; + }; + + priority = mkOption { + type = types.int; + default = 50; + example = 30; + description = '' + Which priority to assign to this cache. Lower number is higher priority. + The official nixpkgs hydra cache is priority 40. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + services.nix-serve = { + enable = true; + + bindAddress = "127.0.0.1"; + + inherit (cfg) + port + secretKeyFile + ; + + package = pkgs.nix-serve-ng; + + extraParams = "--priority=${toString cfg.priority}"; + }; + + my.services.nginx.virtualHosts = [ + { + subdomain = "cache"; + inherit (cfg) port; + } + ]; + }; +} diff --git a/modules/services/paperless/default.nix b/modules/services/paperless/default.nix new file mode 100644 index 0000000..c9d6220 --- /dev/null +++ b/modules/services/paperless/default.nix @@ -0,0 +1,164 @@ +{ 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 = { + requires = [ "postgresql.service" ]; + after = [ "postgresql.service" ]; + + serviceConfig = { + EnvironmentFile = cfg.secretKeyFile; + }; + }; + + paperless-consumer = { + requires = [ "postgresql.service" ]; + after = [ "postgresql.service" ]; + + serviceConfig = { + EnvironmentFile = cfg.secretKeyFile; + }; + }; + + paperless-web = { + requires = [ "postgresql.service" ]; + after = [ "postgresql.service" ]; + + serviceConfig = { + EnvironmentFile = cfg.secretKeyFile; + }; + }; + + paperless-task-queue = { + requires = [ "postgresql.service" ]; + 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/modules/services/sabnzbd/default.nix b/modules/services/sabnzbd/default.nix new file mode 100644 index 0000000..7ab145f --- /dev/null +++ b/modules/services/sabnzbd/default.nix @@ -0,0 +1,57 @@ +# Usenet binary client. +{ config, lib, ... }: +let + cfg = config.my.services.sabnzbd; + port = 9090; # NOTE: not declaratively set... +in +{ + options.my.services.sabnzbd = with lib; { + enable = mkEnableOption "SABnzbd binary news reader"; + }; + + config = lib.mkIf cfg.enable { + services.sabnzbd = { + enable = true; + group = "media"; + }; + + # Set-up media group + users.groups.media = { }; + + my.services.nginx.virtualHosts = [ + { + subdomain = "sabnzbd"; + inherit port; + } + ]; + + services.fail2ban.jails = { + sabnzbd = '' + enabled = true + filter = sabnzbd + port = http,https + # Unfortunately, sabnzbd does not log to systemd journal + backend = auto + logpath = /var/lib/sabnzbd/logs/sabnzbd.log + ''; + }; + + environment.etc = { + # FIXME: path to log file + "fail2ban/filter.d/sabnzbd.conf".text = '' + [Definition] + failregex = ^.*WARNING.*API Key incorrect, Use the api key from Config->General in your 3rd party program: .* \(X-Forwarded-For: \) .*$ + ^.*WARNING.*API Key incorrect, Use the api key from Config->General in your 3rd party program: .*$ + ^.*WARNING.*API Key missing, please enter the api key from Config->General into your 3rd party program: .* \(X-Forwarded-For: \) .*$ + ^.*WARNING.*API Key missing, please enter the api key from Config->General into your 3rd party program: .*$ + ^.*WARNING.*Refused connection from: .* \(X-Forwarded-For: \) .*$ + ^.*WARNING.*Refused connection from: .*$ + ^.*WARNING.*Refused connection with hostname ".*" from: .* \(X-Forwarded-For: \) .*$ + ^.*WARNING.*Refused connection with hostname ".*" from: .*$ + ^.*WARNING.*Unsuccessful login attempt from .* \(X-Forwarded-For: \) .*$ + ^.*WARNING.*Unsuccessful login attempt from .*$ + journalmatch = _SYSTEMD_UNIT=sabnzbd.service + ''; + }; + }; +} diff --git a/services/ssh-server.nix b/modules/services/ssh-server/default.nix similarity index 76% rename from services/ssh-server.nix rename to modules/services/ssh-server/default.nix index a41a673..9ae0fa8 100644 --- a/services/ssh-server.nix +++ b/modules/services/ssh-server/default.nix @@ -12,9 +12,12 @@ in services.openssh = { # Enable the OpenSSH daemon. enable = true; - # Be more secure - permitRootLogin = "no"; - passwordAuthentication = false; + + settings = { + # Be more secure + PermitRootLogin = "no"; + PasswordAuthentication = false; + }; }; # Opens the relevant UDP ports. diff --git a/modules/services/tandoor-recipes/default.nix b/modules/services/tandoor-recipes/default.nix new file mode 100644 index 0000000..d78bef3 --- /dev/null +++ b/modules/services/tandoor-recipes/default.nix @@ -0,0 +1,79 @@ +{ config, lib, ... }: +let + cfg = config.my.services.tandoor-recipes; +in +{ + options.my.services.tandoor-recipes = with lib; { + enable = mkEnableOption "Tandoor Recipes service"; + + port = mkOption { + type = types.port; + default = 4536; + example = 8080; + description = "Internal port for webui"; + }; + + secretKeyFile = mkOption { + type = types.str; + example = "/var/lib/tandoor-recipes/secret-key.env"; + description = '' + Secret key as an 'EnvironmentFile' (see `systemd.exec(5)`) + ''; + }; + }; + + config = lib.mkIf cfg.enable { + services.tandoor-recipes = { + enable = true; + + port = cfg.port; + extraConfig = + let + tandoorRecipesDomain = "recipes.${config.networking.domain}"; + in + { + # Use PostgreSQL + DB_ENGINE = "django.db.backends.postgresql"; + POSTGRES_HOST = "/run/postgresql"; + POSTGRES_USER = "tandoor_recipes"; + POSTGRES_DB = "tandoor_recipes"; + + # Security settings + ALLOWED_HOSTS = tandoorRecipesDomain; + CSRF_TRUSTED_ORIGINS = "https://${tandoorRecipesDomain}"; + + # Misc + TIMEZONE = config.time.timeZone; + }; + }; + + systemd.services = { + tandoor-recipes = { + after = [ "postgresql.service" ]; + + serviceConfig = { + EnvironmentFile = cfg.secretKeyFile; + }; + }; + }; + + # Set-up database + services.postgresql = { + enable = true; + ensureDatabases = [ "tandoor_recipes" ]; + ensureUsers = [ + { + name = "tandoor_recipes"; + ensurePermissions."DATABASE tandoor_recipes" = "ALL PRIVILEGES"; + } + ]; + }; + + my.services.nginx.virtualHosts = [ + { + subdomain = "recipes"; + inherit (cfg) port; + } + ]; + }; +} 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..dcba0aa 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,33 @@ 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"; }; }; + # Transmission wants to eat *all* my RAM if left to its own devices + systemd.services.transmission = { + serviceConfig = { + MemoryMax = "33%"; + }; + }; + + # 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/modules/services/vikunja/default.nix b/modules/services/vikunja/default.nix new file mode 100644 index 0000000..1cdef5f --- /dev/null +++ b/modules/services/vikunja/default.nix @@ -0,0 +1,123 @@ +# Todo and kanban app +{ config, lib, ... }: +let + cfg = config.my.services.vikunja; + subdomain = "todo"; + vikunjaDomain = "${subdomain}.${config.networking.domain}"; + socketPath = "/run/vikunja/vikunja.socket"; +in +{ + options.my.services.vikunja = with lib; { + enable = mkEnableOption "Vikunja todo app"; + + mail = { + enable = mkEnableOption { + description = "mailer configuration"; + }; + + configFile = mkOption { + type = types.str; + example = "/run/secrets/vikunja-mail-config.env"; + description = "Configuration for the mailer connection, using environment variables."; + }; + }; + }; + + config = lib.mkIf cfg.enable { + services.vikunja = { + enable = true; + + frontendScheme = "https"; + frontendHostname = vikunjaDomain; + + setupNginx = false; + + database = { + type = "postgres"; + user = "vikunja"; + database = "vikunja"; + host = "/run/postgresql"; + }; + + settings = { + service = { + # Only allow registration of users through the CLI + enableregistration = false; + # Ues the host's timezone + timezone = config.time.timeZone; + # Use UNIX socket for serving the API + unixsocket = socketPath; + unixsocketmode = "0o660"; + }; + + mailer = { + enabled = cfg.mail.enable; + }; + }; + + environmentFiles = lib.optional cfg.mail.enable cfg.mail.configFile; + }; + + # This is a weird setup + my.services.nginx.virtualHosts = [ + { + inherit subdomain; + # Serve the root for the web-ui + root = config.services.vikunja.package-frontend; + + extraConfig = { + locations = { + "/" = { + tryFiles = "try_files $uri $uri/ /"; + }; + + # Serve the API through a UNIX socket + "~* ^/(api|dav|\\.well-known)/" = { + proxyPass = "http://unix:${socketPath}"; + extraConfig = '' + client_max_body_size 20M; + ''; + }; + }; + }; + } + ]; + + systemd.services.vikunja-api = { + serviceConfig = { + # Use a system user to simplify using the CLI + DynamicUser = lib.mkForce false; + # Set the user for postgres authentication + User = "vikunja"; + # Create /run/vikunja/ to serve the UNIX socket + RuntimeDirectory = "vikunja"; + }; + }; + + users.users.vikunja = { + description = "Vikunja Service"; + group = "vikunja"; + isSystemUser = true; + }; + users.groups.vikunja = { }; + + # Allow nginx to access the UNIX socket + users.users.nginx.extraGroups = [ "vikunja" ]; + + services.postgresql = { + ensureDatabases = [ "vikunja" ]; + ensureUsers = [ + { + name = "vikunja"; + ensurePermissions = { "DATABASE vikunja" = "ALL PRIVILEGES"; }; + } + ]; + }; + + my.services.backup = { + paths = [ + config.services.vikunja.settings.files.basepath + ]; + }; + }; +} diff --git a/services/wireguard.nix b/modules/services/wireguard/default.nix similarity index 91% rename from services/wireguard.nix rename to modules/services/wireguard/default.nix index fc948f6..fc5518d 100644 --- a/services/wireguard.nix +++ b/modules/services/wireguard/default.nix @@ -5,9 +5,34 @@ { config, lib, pkgs, ... }: let cfg = config.my.services.wireguard; + secrets = config.age.secrets; hostName = config.networking.hostName; - peers = config.my.secrets.wireguard.peers; + peers = { + # "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 +43,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 +51,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; + privateKeyFile = secrets."wireguard/private-key".path; peers = let diff --git a/modules/services/wireguard/keys/milady/private-key.age b/modules/services/wireguard/keys/milady/private-key.age new file mode 100644 index 0000000..fb84f91 --- /dev/null +++ b/modules/services/wireguard/keys/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/services/wireguard/keys/richelieu/private-key.age b/modules/services/wireguard/keys/richelieu/private-key.age new file mode 100644 index 0000000..e796688 --- /dev/null +++ b/modules/services/wireguard/keys/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/modules/services/wireguard/keys/secrets.nix b/modules/services/wireguard/keys/secrets.nix new file mode 100644 index 0000000..3985477 --- /dev/null +++ b/modules/services/wireguard/keys/secrets.nix @@ -0,0 +1,15 @@ +# Extra wireguard keys that are not hosts NixOS hosts +let + keys = import ../../../../keys; + + all = [ + keys.users.ambroisie + ]; +in +{ + # Sarah's iPhone + "milady/private-key.age".publicKeys = all; + + # My Android phone + "richelieu/private-key.age".publicKeys = all; +} diff --git a/modules/services/woodpecker/agent-docker/default.nix b/modules/services/woodpecker/agent-docker/default.nix new file mode 100644 index 0000000..b18d075 --- /dev/null +++ b/modules/services/woodpecker/agent-docker/default.nix @@ -0,0 +1,45 @@ +{ config, lib, ... }: +let + cfg = config.my.services.woodpecker; + + hasRunner = (name: builtins.elem name cfg.runners); +in +{ + config = lib.mkIf (cfg.enable && hasRunner "docker") { + services.woodpecker-agents = { + agents.docker = { + enable = true; + + environment = { + WOODPECKER_SERVER = "localhost:${toString cfg.rpcPort}"; + WOODPECKER_MAX_WORKFLOWS = "10"; + WOODPECKER_BACKEND = "docker"; + WOODPECKER_FILTER_LABELS = "type=docker"; + WOODPECKER_HEALTHCHECK = "false"; + }; + + environmentFile = [ cfg.sharedSecretFile ]; + + extraGroups = [ "docker" ]; + }; + }; + + # Make sure it is activated in that case + my.system.docker.enable = true; + + # FIXME: figure out the issue + services.unbound.resolveLocalQueries = false; + + # Adjust runner service for nix usage + systemd.services.woodpecker-agent-docker = { + after = [ "docker.socket" ]; # Needs the socket to be available + # might break deployment + restartIfChanged = false; + serviceConfig = { + BindPaths = [ + "/var/run/docker.sock" + ]; + }; + }; + }; +} diff --git a/modules/services/woodpecker/agent-exec/default.nix b/modules/services/woodpecker/agent-exec/default.nix new file mode 100644 index 0000000..ad30188 --- /dev/null +++ b/modules/services/woodpecker/agent-exec/default.nix @@ -0,0 +1,64 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.services.woodpecker; + + hasRunner = (name: builtins.elem name cfg.runners); +in +{ + config = lib.mkIf (cfg.enable && hasRunner "exec") { + services.woodpecker-agents = { + agents.exec = { + enable = true; + + environment = { + WOODPECKER_SERVER = "localhost:${toString cfg.rpcPort}"; + WOODPECKER_MAX_WORKFLOWS = "10"; + WOODPECKER_BACKEND = "local"; + WOODPECKER_FILTER_LABELS = "type=exec"; + WOODPECKER_HEALTHCHECK = "false"; + + NIX_REMOTE = "daemon"; + PAGER = "cat"; + }; + + environmentFile = [ cfg.sharedSecretFile ]; + }; + }; + + # Adjust runner service for nix usage + systemd.services.woodpecker-agent-exec = { + # Might break deployment + restartIfChanged = false; + + path = with pkgs; [ + woodpecker-plugin-git + bash + coreutils + git + git-lfs + gnutar + gzip + nix + ]; + + serviceConfig = { + # Same option as upstream, without @setuid + SystemCallFilter = lib.mkForce "~@clock @privileged @cpu-emulation @debug @keyring @module @mount @obsolete @raw-io @reboot @swap"; + + BindPaths = [ + "/nix/var/nix/daemon-socket/socket" + "/run/nscd/socket" + ]; + BindReadOnlyPaths = [ + "/etc/passwd:/etc/passwd" + "/etc/group:/etc/group" + "/etc/nix:/etc/nix" + "${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/" + ]; + }; + }; + }; +} diff --git a/modules/services/woodpecker/default.nix b/modules/services/woodpecker/default.nix new file mode 100644 index 0000000..34ffca6 --- /dev/null +++ b/modules/services/woodpecker/default.nix @@ -0,0 +1,46 @@ +{ lib, ... }: +{ + imports = [ + ./agent-docker + ./agent-exec + ./server + ]; + + options.my.services.woodpecker = with lib; { + enable = mkEnableOption "Woodpecker 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 Woodpecker UI"; + }; + rpcPort = mkOption { + type = types.port; + default = 3031; + example = 8080; + description = "Internal port of the Woodpecker UI"; + }; + secretFile = mkOption { + type = types.str; + example = "/run/secrets/woodpecker.env"; + description = "Secrets to inject into Woodpecker server"; + }; + sharedSecretFile = mkOption { + type = types.str; + example = "/run/secrets/woodpecker.env"; + description = "Shared RPC secret to inject into server and runners"; + }; + }; +} diff --git a/modules/services/woodpecker/server/default.nix b/modules/services/woodpecker/server/default.nix new file mode 100644 index 0000000..152e707 --- /dev/null +++ b/modules/services/woodpecker/server/default.nix @@ -0,0 +1,66 @@ +{ config, lib, ... }: +let + cfg = config.my.services.woodpecker; +in +{ + config = lib.mkIf cfg.enable { + services.woodpecker-server = { + enable = true; + + environment = { + WOODPECKER_OPEN = "true"; + WOODPECKER_HOST = "https://woodpecker.${config.networking.domain}"; + WOODPECKER_DATABASE_DRIVER = "postgres"; + WOODPECKER_DATABASE_DATASOURCE = "postgres:///woodpecker?host=/run/postgresql"; + WOODPECKER_ADMIN = "${cfg.admin}"; + WOODPECKER_SERVER_ADDR = ":${toString cfg.port}"; + WOODPECKER_GRPC_ADDR = ":${toString cfg.rpcPort}"; + + WOODPECKER_GITEA = "true"; + WOODPECKER_GITEA_URL = config.services.gitea.settings.server.ROOT_URL; + + WOODPECKER_LOG_LEVEL = "debug"; + }; + }; + + systemd.services.woodpecker-server = { + serviceConfig = { + # Set username for DB access + User = "woodpecker"; + + BindPaths = [ + # Allow access to DB path + "/run/postgresql" + ]; + + EnvironmentFile = [ + cfg.secretFile + cfg.sharedSecretFile + ]; + }; + }; + + services.postgresql = { + enable = true; + ensureDatabases = [ "woodpecker" ]; + ensureUsers = [{ + name = "woodpecker"; + ensurePermissions = { + "DATABASE woodpecker" = "ALL PRIVILEGES"; + }; + }]; + }; + + my.services.nginx.virtualHosts = [ + { + subdomain = "woodpecker"; + inherit (cfg) port; + } + # I might want to be able to RPC from other hosts in the future + { + subdomain = "woodpecker-rpc"; + port = cfg.rpcPort; + } + ]; + }; +} diff --git a/modules/system/boot/default.nix b/modules/system/boot/default.nix new file mode 100644 index 0000000..3d8495e --- /dev/null +++ b/modules/system/boot/default.nix @@ -0,0 +1,23 @@ +{ 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 = { + tmp = { + cleanOnBoot = cfg.tmp.clean; + + useTmpfs = cfg.tmp.tmpfs; + }; + }; + }; +} diff --git a/modules/system/default.nix b/modules/system/default.nix new file mode 100644 index 0000000..9fe3b57 --- /dev/null +++ b/modules/system/default.nix @@ -0,0 +1,15 @@ +# System-related modules +{ ... }: + +{ + imports = [ + ./boot + ./docker + ./documentation + ./language + ./nix + ./packages + ./podman + ./users + ]; +} diff --git a/modules/system/docker/default.nix b/modules/system/docker/default.nix new file mode 100644 index 0000000..f051814 --- /dev/null +++ b/modules/system/docker/default.nix @@ -0,0 +1,27 @@ +# Podman related settings +{ config, lib, ... }: +let + cfg = config.my.system.docker; +in +{ + options.my.system.docker = with lib; { + enable = mkEnableOption "docker configuration"; + }; + + config = lib.mkIf cfg.enable { + virtualisation.docker = { + enable = true; + + # Remove unused data on a weekly basis + autoPrune = { + enable = true; + + dates = "weekly"; + + flags = [ + "--all" + ]; + }; + }; + }; +} diff --git a/modules/system/documentation/default.nix b/modules/system/documentation/default.nix new file mode 100644 index 0000000..304c811 --- /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; { + enable = my.mkDisableOption "Documentation integration"; + + dev.enable = my.mkDisableOption "Documentation aimed at developers"; + + info.enable = my.mkDisableOption "Documentation aimed at developers"; + + man = { + enable = my.mkDisableOption "Documentation aimed at developers"; + + linux = my.mkDisableOption "Linux man pages (section 2 & 3)"; + }; + + nixos.enable = my.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..47d6499 --- /dev/null +++ b/modules/system/nix/default.nix @@ -0,0 +1,107 @@ +# Nix related settings +{ config, inputs, lib, options, pkgs, ... }: +let + cfg = config.my.system.nix; + + channels = lib.my.merge [ + { + # Allow me to use my custom package using `nix run self#pkg` + self = inputs.self; + # Add NUR to run some packages that are only present there + nur = inputs.nur; + # Use pinned nixpkgs when using `nix run pkgs#` + pkgs = inputs.nixpkgs; + } + (lib.optionalAttrs cfg.inputs.overrideNixpkgs { + # ... And with `nix run nixpkgs#` + nixpkgs = inputs.nixpkgs; + }) + ]; +in +{ + options.my.system.nix = with lib; { + enable = my.mkDisableOption "nix configuration"; + + cache = { + selfHosted = my.mkDisableOption "self-hosted cache"; + }; + + inputs = { + link = my.mkDisableOption "link inputs to `/etc/nix/inputs/`"; + + addToRegistry = my.mkDisableOption "add inputs and self to registry"; + + addToNixPath = my.mkDisableOption "add inputs and self to nix path"; + + overrideNixpkgs = my.mkDisableOption "point nixpkgs to pinned system version"; + }; + }; + + config = lib.mkIf cfg.enable (lib.mkMerge [ + { + assertions = [ + { + assertion = cfg.inputs.addToNixPath -> cfg.inputs.link; + message = '' + enabling `my.system.nix.inputs.addToNixPath` needs to have + `my.system.nix.inputs.link = true` + ''; + } + ]; + } + + { + nix = { + package = pkgs.nix; + + settings = { + experimental-features = [ "nix-command" "flakes" ]; + }; + }; + } + + (lib.mkIf cfg.cache.selfHosted { + nix = { + settings = { + # The NixOS module adds the official Hydra cache by default + # No need to use `extra-*` options. + substituters = [ + "https://cache.belanyi.fr/" + ]; + + trusted-public-keys = [ + "cache.belanyi.fr:LPhrTqufwfxTceg1nRWueDWf7/2zSVY9K00pq2UI7tw=" + ]; + }; + }; + }) + + (lib.mkIf cfg.inputs.addToRegistry { + nix.registry = + let + makeEntry = v: { flake = v; }; + makeEntries = lib.mapAttrs (lib.const makeEntry); + in + makeEntries channels; + }) + + (lib.mkIf cfg.inputs.link { + environment.etc = + let + makeLink = n: v: { + name = "nix/inputs/${n}"; + value = { source = v.outPath; }; + }; + makeLinks = lib.mapAttrs' makeLink; + in + makeLinks channels; + }) + + (lib.mkIf cfg.inputs.addToNixPath { + nix.nixPath = [ + "/etc/nix/inputs" + ] + ++ options.nix.nixPath.default; + }) + ]); +} diff --git a/modules/system/packages/default.nix b/modules/system/packages/default.nix new file mode 100644 index 0000000..5c29aa0 --- /dev/null +++ b/modules/system/packages/default.nix @@ -0,0 +1,35 @@ +# Common packages +{ config, lib, pkgs, ... }: +let + cfg = config.my.system.packages; +in +{ + options.my.system.packages = with lib; { + enable = my.mkDisableOption "packages configuration"; + + allowAliases = mkEnableOption "allow package aliases"; + + 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 = { + inherit (cfg) allowAliases allowUnfree; + }; + }; +} diff --git a/modules/system/podman/default.nix b/modules/system/podman/default.nix new file mode 100644 index 0000000..52630c7 --- /dev/null +++ b/modules/system/podman/default.nix @@ -0,0 +1,48 @@ +# Podman related settings +{ config, lib, ... }: +let + cfg = config.my.system.podman; +in +{ + options.my.system.podman = with lib; { + enable = mkEnableOption "podman configuration"; + }; + + config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = cfg.enable -> !config.my.system.docker.enable; + message = '' + `config.my.system.podman` is incompatible with + `config.my.system.docker`. + ''; + } + ]; + + 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.settings = { + dns_enabled = true; + }; + + # Remove unused data on a weekly basis + autoPrune = { + enable = true; + + dates = "weekly"; + + flags = [ + "--all" + ]; + }; + }; + }; +} diff --git a/modules/system/users/default.nix b/modules/system/users/default.nix new file mode 100644 index 0000000..27557bd --- /dev/null +++ b/modules/system/users/default.nix @@ -0,0 +1,51 @@ +# 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 + "docker" # usage of `docker` socket + "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..683e021 --- /dev/null +++ b/overlays/default.nix @@ -0,0 +1,6 @@ +# Automatically import all overlays in the directory +let + files = builtins.readDir ./.; + overlays = builtins.removeAttrs files [ "default.nix" ]; +in +builtins.mapAttrs (name: _: import "${./.}/${name}") overlays 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..9b959f5 --- /dev/null +++ b/pkgs/bw-pass/default.nix @@ -0,0 +1,43 @@ +{ lib, bitwarden-cli, coreutils, jq, keyutils, makeWrapper, rofi, stdenvNoCC }: +stdenvNoCC.mkDerivation rec { + pname = "bw-pass"; + version = "0.1.0"; + + src = ./bw-pass; + + nativeBuildInputs = [ + 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://git.belanyi.fr/ambroisie/nix-config"; + license = with licenses; [ mit ]; + mainProgram = "bw-pass"; + maintainers = with maintainers; [ ambroisie ]; + platforms = platforms.linux; + }; +} 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..12814e1 --- /dev/null +++ b/pkgs/change-audio/default.nix @@ -0,0 +1,42 @@ +{ lib, libnotify, makeWrapper, pamixer, stdenvNoCC }: +stdenvNoCC.mkDerivation rec { + pname = "change-audio"; + version = "0.3.0"; + + src = ./change-audio; + + nativeBuildInputs = [ + 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://git.belanyi.fr/ambroisie/nix-config"; + license = with licenses; [ mit ]; + mainProgram = "change-audio"; + maintainers = with maintainers; [ ambroisie ]; + platforms = platforms.linux; + }; +} 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..1f8c88c --- /dev/null +++ b/pkgs/change-backlight/default.nix @@ -0,0 +1,42 @@ +{ lib, brightnessctl, libnotify, makeWrapper, stdenvNoCC }: +stdenvNoCC.mkDerivation rec { + pname = "change-backlight"; + version = "0.1.0"; + + src = ./change-backlight; + + nativeBuildInputs = [ + 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://git.belanyi.fr/ambroisie/nix-config"; + license = with licenses; [ mit ]; + mainProgram = "change-backlight"; + maintainers = with maintainers; [ ambroisie ]; + platforms = platforms.linux; + }; +} 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..32e09d0 --- /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; + + nativeBuildInputs = [ + 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; { + description = "A simple script inspired by Shopify's comma, for modern Nix"; + homepage = "https://git.belanyi.fr/ambroisie/nix-config"; + license = with licenses; [ mit ]; + mainProgram = ","; + maintainers = with maintainers; [ ambroisie ]; + platforms = platforms.unix; + }; +} diff --git a/pkgs/default.nix b/pkgs/default.nix index 58f004a..c9e755a 100644 --- a/pkgs/default.nix +++ b/pkgs/default.nix @@ -1,18 +1,40 @@ { 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 { }; - havm = pkgs.callPackage ./havm { }; + dragger = pkgs.callPackage ./dragger { }; + + drone-rsync = pkgs.callPackage ./drone-rsync { }; + + drone-scp = pkgs.callPackage ./drone-scp { }; + + ff2mpv-go = pkgs.callPackage ./ff2mpv-go { }; + + i3-get-window-criteria = pkgs.callPackage ./i3-get-window-criteria { }; lohr = pkgs.callPackage ./lohr { }; - nolimips = pkgs.callPackage ./nolimips { }; + matrix-notifier = pkgs.callPackage ./matrix-notifier { }; - podgrab = pkgs.callPackage ./podgrab { }; + osc52 = pkgs.callPackage ./osc52 { }; - unbound-zones-adblock = pkgs.callPackage ./unbound-zones-adblock { - inherit unified-hosts-lists; - }; + rbw-pass = pkgs.callPackage ./rbw-pass { }; + + 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 { }; + + wifi-qr = pkgs.callPackage ./wifi-qr { }; +}) diff --git a/pkgs/diff-flake/default.nix b/pkgs/diff-flake/default.nix index 9511952..9cccd20 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.4.0"; src = ./diff-flake; - phases = [ "buildPhase" "installPhase" "fixupPhase" ]; - - buildInputs = [ + nativeBuildInputs = [ makeWrapper - shellcheck ]; - buildPhase = '' - shellcheck $src - ''; + dontUnpack = true; + + dontBuild = true; installPhase = '' mkdir -p $out/bin @@ -35,8 +32,10 @@ stdenvNoCC.mkDerivation rec { meta = with lib; { description = "Nix flake helper to visualize changes in closures"; - homepage = "https://gitea.belanyi.fr/ambroisie/nix-config"; + homepage = "https://git.belanyi.fr/ambroisie/nix-config"; license = with licenses; [ mit ]; + mainProgram = "diff-flake"; + maintainers = with maintainers; [ ambroisie ]; platforms = platforms.unix; }; } diff --git a/pkgs/diff-flake/diff-flake b/pkgs/diff-flake/diff-flake index ef03122..0572b4e 100755 --- a/pkgs/diff-flake/diff-flake +++ b/pkgs/diff-flake/diff-flake @@ -20,19 +20,21 @@ sanitize_output() { fi } -add_shell() { - local SYSTEM - if [ $# -gt 0 ] && [ -n "$1" ]; then - SYSTEM="$1" - else - 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") +current_system() { + nix eval --raw --impure --expr 'builtins.currentSystem' +} + +add_home() { + FLAKE_OUTPUTS+=("homeConfigurations.\"$1\".activationPackage") } add_host() { - FLAKE_OUTPUTS+=("nixosConfigurations.$1.config.system.build.toplevel") + FLAKE_OUTPUTS+=("nixosConfigurations.\"$1\".config.system.build.toplevel") +} + +add_shell() { + # Use 'inputDerivation' attribute to make sure that it is build-able + FLAKE_OUTPUTS+=("devShells.\"$(current_system)\".\"$1\".inputDerivation") } usage() { @@ -52,17 +54,21 @@ usage() { print_err " -p, --previous-rev" print_err " which git revision should be considered the 'previous' state," print_err " defaults to HEAD~" + print_err " --home [name]" + print_err " specify the name of a home-manager output configuration whose" + print_err " closure should be diffed, can be used multiple times" + print_err " if no configuration name is given, defaults to current username" print_err " --host [name]" print_err " specify the name of a NixOS output configuration whose" print_err " closure should be diffed, can be used multiple times" print_err " if no host name is given, defaults to current hostname" - print_err " --shell [system]" - print_err " specify a specific system's devShell output whose closure" + print_err " --shell [name]" + print_err " specify a specific devShell configuration name whose closure" print_err " should be diffed, can be used multiple times" - print_err " if no system is given, defaults to current system" + print_err " if no name is given, defaults to 'default'" print_err "" print_err "when no flake outputs are specified, automatically queries for" - print_err "all NixOS configurations, and devShell for current system" + print_err "all NixOS configurations, and devShells for current system" } is_option() { @@ -95,6 +101,14 @@ parse_args() { PREVIOUS_REV="$(git rev-parse "$1")" shift ;; + --home) + if [ $# -gt 0 ] && ! is_option "$1"; then + add_home "$1" + shift + else + add_home "$USER" + fi + ;; --host) if [ $# -gt 0 ] && ! is_option "$1"; then add_host "$1" @@ -108,7 +122,7 @@ parse_args() { add_shell "$1" shift else - add_shell + add_shell "default" fi ;; --) @@ -124,12 +138,24 @@ parse_args() { done } +list_home_configurations() { + nix eval '.#homeConfigurations' \ + --apply 'attrs: with builtins; concatStringsSep "\n" (attrNames attrs)' \ + --raw +} + list_nixos_configurations() { nix eval '.#nixosConfigurations' \ --apply 'attrs: with builtins; concatStringsSep "\n" (attrNames attrs)' \ --raw } +list_dev_shells() { + nix eval ".#devShells.\"$(current_system)\"" \ + --apply 'attrs: with builtins; concatStringsSep "\n" (attrNames attrs)' \ + --raw +} + diff_output() { local PREV NEW; PREV="$(mktemp --dry-run)" @@ -149,10 +175,15 @@ diff_output() { parse_args "$@" if [ "${#FLAKE_OUTPUTS[@]}" -eq 0 ]; then + for home in $(list_home_configurations); do + add_home "$home" + done for host in $(list_nixos_configurations); do add_host "$host" done - add_shell + for shell in $(list_dev_shells); do + add_shell "$shell" + done fi for out in "${FLAKE_OUTPUTS[@]}"; do diff --git a/pkgs/dragger/default.nix b/pkgs/dragger/default.nix new file mode 100644 index 0000000..9eda7df --- /dev/null +++ b/pkgs/dragger/default.nix @@ -0,0 +1,30 @@ +{ lib, fetchFromGitHub, qt5, }: +qt5.mkDerivation rec { + pname = "dragger"; + version = "0.1.0"; + + src = fetchFromGitHub { + owner = "ambroisie"; + repo = "dragger"; + rev = "v${version}"; + hash = "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://git.belanyi.fr/ambroisie/dragger"; + license = licenses.mit; + mainProgram = "dragger"; + 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..a7c2cbc --- /dev/null +++ b/pkgs/drone-rsync/default.nix @@ -0,0 +1,41 @@ +{ lib, makeWrapper, openssh, rsync, sshpass, stdenvNoCC }: +stdenvNoCC.mkDerivation rec { + pname = "drone-rsync"; + version = "0.1.0"; + + src = ./drone-rsync; + + nativeBuildInputs = [ + 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://git.belanyi.fr/ambroisie/nix-config"; + license = with licenses; [ mit ]; + mainProgram = "drone-rsync"; + maintainers = with maintainers; [ ambroisie ]; + platforms = platforms.unix; + }; +} 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..7437b06 --- /dev/null +++ b/pkgs/drone-scp/default.nix @@ -0,0 +1,25 @@ +{ lib, buildGoModule, fetchFromGitHub }: +buildGoModule rec { + pname = "drone-scp"; + version = "1.6.3"; + + src = fetchFromGitHub { + owner = "appleboy"; + repo = "drone-scp"; + rev = "v${version}"; + hash = "sha256-ELjPqoRR4O6gmc/PgthQuSXuSTQNzBZoAUT80zVVbV0="; + }; + + vendorHash = "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; + mainProgram = "drone-scp"; + }; +} diff --git a/pkgs/ff2mpv-go/default.nix b/pkgs/ff2mpv-go/default.nix new file mode 100644 index 0000000..8c59bf5 --- /dev/null +++ b/pkgs/ff2mpv-go/default.nix @@ -0,0 +1,31 @@ +{ 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}"; + hash = "sha256-e/AuOA3isFTyBf97Zwtr16yo49UdYzvktV5PKB/eH/s="; + }; + + vendorHash = null; + + postPatch = '' + sed -i -e 's,"mpv","${lib.getExe 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; + mainProgram = "ff2mpv-go"; + }; +} diff --git a/pkgs/havm/default.nix b/pkgs/havm/default.nix deleted file mode 100644 index 20817fc..0000000 --- a/pkgs/havm/default.nix +++ /dev/null @@ -1,30 +0,0 @@ -{ fetchurl, ghc, lib, stdenv, which }: -stdenv.mkDerivation rec { - pname = "havm"; - version = "0.28"; - - src = fetchurl { - url = "https://www.lrde.epita.fr/~tiger/download/${pname}-${version}.tar.gz"; - sha256 = "sha256-FDi4FZ8rjGqRkFlROtcJsv+mks7MmIXQGV4bZrwkQrA="; - }; - - buildInputs = [ - ghc - which # Used by tests - ]; - - doCheck = true; - - meta = with lib; { - description = "A simple virtual machine to execute Andrew Appel's HIR/LIR"; - longDescription = '' - HAVM is a virtual machine designed to execute simple register based high - level intermediate code. It is based on the intermediate representations - ("canonicalized" or not) defined by Andrew Appel in his "Modern Compiler - Implementation". - ''; - homepage = "https://www.lrde.epita.fr/wiki/Havm"; - license = licenses.gpl2Plus; - platforms = platforms.all; - }; -} diff --git a/pkgs/i3-get-window-criteria/default.nix b/pkgs/i3-get-window-criteria/default.nix new file mode 100644 index 0000000..2fc840d --- /dev/null +++ b/pkgs/i3-get-window-criteria/default.nix @@ -0,0 +1,42 @@ +{ lib, coreutils, gnused, makeWrapper, stdenvNoCC, xorg }: +stdenvNoCC.mkDerivation rec { + pname = "i3-get-window-criteria"; + version = "0.1.0"; + + src = ./i3-get-window-criteria; + + nativeBuildInputs = [ + 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://git.belanyi.fr/ambroisie/nix-config"; + license = with licenses; [ mit ]; + mainProgram = "i3-get-window-criteria"; + maintainers = with maintainers; [ ambroisie ]; + platforms = platforms.unix; + }; +} 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..a83b092 100644 --- a/pkgs/lohr/default.nix +++ b/pkgs/lohr/default.nix @@ -1,21 +1,23 @@ -{ fetchFromGitHub, lib, rustPlatform }: +{ lib, fetchFromGitHub, rustPlatform }: rustPlatform.buildRustPackage rec { pname = "lohr"; - version = "0.4.0"; + version = "0.4.2"; src = fetchFromGitHub { owner = "alarsyo"; repo = "lohr"; rev = "v${version}"; - sha256 = "sha256-MplTVJG+SoeLMXQP+ix/zM3OSHuQmZnunn900YnyCBw="; + hash = "sha256-2pN/Me5fCdE++TzBUswPXzjuUIIB7Uck+Scp361JgE4="; }; - cargoSha256 = "sha256-iuMJj8tqetlmdfsrfudnU1afwUzjls/UdYLq1u0gr+g="; + cargoHash = "sha256-YHg4b6rKcnVJSDoWh9/o+p40NBog65Gd2/UwIDXiUe0="; meta = with lib; { description = "Git mirroring daemon"; homepage = "https://github.com/alarsyo/lohr"; license = with licenses; [ mit asl20 ]; + mainProgram = "lohr"; + maintainers = with maintainers; [ ambroisie ]; platforms = platforms.unix; }; } diff --git a/pkgs/matrix-notifier/default.nix b/pkgs/matrix-notifier/default.nix new file mode 100644 index 0000000..a96cb61 --- /dev/null +++ b/pkgs/matrix-notifier/default.nix @@ -0,0 +1,45 @@ +{ lib, curl, jq, fetchFromGitHub, makeWrapper, pandoc, stdenvNoCC }: +stdenvNoCC.mkDerivation rec { + pname = "matrix-notifier"; + version = "0.3.0"; + + src = fetchFromGitHub { + owner = "ambroisie"; + repo = "matrix-notifier"; + rev = "v${version}"; + hash = "sha256-NE9RO0ep2ibrT9EUPGTnUE3ofdNTCHwelxnX9tCflg0="; + }; + + 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://git.belanyi.fr/ambroisie/${pname}"; + license = licenses.mit; + mainProgram = "matrix-notifier"; + maintainers = with maintainers; [ ambroisie ]; + platforms = platforms.unix; + }; +} diff --git a/pkgs/nolimips/default.nix b/pkgs/nolimips/default.nix deleted file mode 100644 index ff5c9b0..0000000 --- a/pkgs/nolimips/default.nix +++ /dev/null @@ -1,23 +0,0 @@ -{ fetchurl, gnulib, lib, stdenv }: -stdenv.mkDerivation rec { - pname = "nolimips"; - version = "0.11"; - - src = fetchurl { - url = "https://www.lrde.epita.fr/~tiger/download/${pname}-${version}.tar.gz"; - sha256 = "sha256-OjbfcBwCZtFP0usz8YXA0lN8xs0jS4I19mkh9p7VHc8="; - }; - - doCheck = true; - - meta = with lib; { - description = "A basic MIPS architecture simulator"; - longDescription = '' - A basic MIPS architecture simulator, which implements a few system calls - and supports an arbitrary number of registers. - ''; - homepage = "https://www.lrde.epita.fr/wiki/Nolimips"; - license = licenses.gpl2; - platforms = platforms.all; - }; -} diff --git a/pkgs/osc52/default.nix b/pkgs/osc52/default.nix new file mode 100644 index 0000000..d4b0c08 --- /dev/null +++ b/pkgs/osc52/default.nix @@ -0,0 +1,41 @@ +{ lib, coreutils, makeWrapper, stdenvNoCC }: +stdenvNoCC.mkDerivation rec { + pname = "osc52"; + version = "0.1.0"; + + src = ./osc52; + + nativeBuildInputs = [ + makeWrapper + ]; + + dontUnpack = true; + + dontBuild = true; + + installPhase = '' + mkdir -p $out/bin + cp $src $out/bin/osc52 + chmod a+x $out/bin/osc52 + ''; + + wrapperPath = lib.makeBinPath [ + coreutils + ]; + + fixupPhase = '' + patchShebangs $out/bin/osc52 + wrapProgram $out/bin/osc52 --prefix PATH : "${wrapperPath}" + ''; + + meta = with lib; { + description = '' + A script to copy strings using the OSC52 escape sequence + ''; + homepage = "https://git.belanyi.fr/ambroisie/nix-config"; + license = with licenses; [ mit ]; + mainProgram = "osc52"; + maintainers = with maintainers; [ ambroisie ]; + platforms = platforms.linux; + }; +} diff --git a/pkgs/osc52/osc52 b/pkgs/osc52/osc52 new file mode 100755 index 0000000..f64ccb6 --- /dev/null +++ b/pkgs/osc52/osc52 @@ -0,0 +1,153 @@ +#!/usr/bin/env bash + +# Max length of the OSC 52 sequence. +: "${OSC52_MAX_SEQUENCE:=100000}" +# Whether to disable tmux/screen DCS escape sequences +: "${OSC52_NO_DCS:=0}" + +die() { + echo "ERROR: $*" + exit 1 +} + +usage() { + if [ $# -gt 0 ]; then + exec 1>&2 + fi + + cat << EOF +Usage: $0 [options] [string] +Send an arbitrary string to the terminal clipboard using the OSC 52 escape +sequence as specified in xterm: + https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands + Section "Operating System Commands", Ps => 52. +The data can either be read from stdin: + $ echo "hello world" | $0 +Or specified on the command line: + $ $0 "hello world" +Options: + -h, --help This screen. + -d, --no-dcs Disable tmux/screen specific DCS sequences, only use OSC 52 + -f, --force Ignore max byte limit (${OSC52_MAX_SEQUENCE}) + -- Stop options processing +EOF + + if [ $# -gt 0 ]; then + echo + die "$@" + else + exit 0 + fi +} + +tmux_seq() { + # shellcheck disable=1003 + printf '\033Ptmux;\033%s\033\\' "$1" +} + +screen_seq() { + # Screen limits the length of string sequences, so we have to break it up. + # Going by the screen history: + # (v4.2.1) Apr 2014 - today: 768 bytes + # Aug 2008 - Apr 2014 (v4.2.0): 512 bytes + # ??? - Aug 2008 (v4.0.3): 256 bytes + local limit=768 + # We go 4 bytes under the limit because we're going to insert two bytes + # before (\eP) and 2 bytes after (\e\) each string. + printf '%s' "$1" | + sed -E "s:.{$((limit - 4))}:&\n:g" | + sed -E -e 's:^:\x1bP:' -e 's:$:\x1b\\:' | + tr -d '\n' +} + +osc52_seq() { + printf '%s' "$1" +} + +print_seq() { + local seq="$1" + + if [ "${OSC52_NO_DCS}" != 0 ]; then + # Override TERM to avoid tmux/screen DCS escape logic + TERM=dummy + fi + + case ${TERM-} in + screen*) + # Since tmux defaults to setting TERM=screen, special case it. + if [ -n "${TMUX-}" ]; then + tmux_seq "${seq}" + else + screen_seq "${seq}" + fi + ;; + tmux*) + tmux_seq "${seq}" + ;; + *) + osc52_seq "${seq}" + ;; + esac +} + +b64enc() { + base64 | tr -d '\n' +} + +copy() { + local str + if [ $# -eq 0 ]; then + str="$(b64enc)" + else + str="$(printf '%s' "$1" | b64enc)" + fi + + if [ "${OSC52_MAX_SEQUENCE}" -gt 0 ]; then + local len=${#str} + if [ "${len}" -gt "${OSC52_MAX_SEQUENCE}" ]; then + die "selection too long to send to terminal:" \ + "${OSC52_MAX_SEQUENCE} limit, ${len} attempted" + fi + fi + + print_seq "$(printf '\033]52;c;%s\a' "${str}")" +} + +main() { + set -e + + local args=() + while [ $# -gt 0 ]; do + case $1 in + -h | --help) + usage + ;; + -f | --force) + OSC52_MAX_SEQUENCE=0 + ;; + -d | --no-dcs) + OSC52_NO_DCS=1 + ;; + --) + shift + args+=("$@") + break + ;; + -*) + usage "Unknown option: $1" + ;; + *) + args+=("$1") + ;; + esac + shift + done + + if [ "${#args[@]}" -gt 1 ]; then + usage "Only supply one argument" + fi + + copy "${args[@]}" +} + +main "$@" 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/rbw-pass/default.nix b/pkgs/rbw-pass/default.nix new file mode 100644 index 0000000..6f9ff0a --- /dev/null +++ b/pkgs/rbw-pass/default.nix @@ -0,0 +1,41 @@ +{ lib, coreutils, makeWrapper, rbw, rofi, stdenvNoCC }: +stdenvNoCC.mkDerivation rec { + pname = "rbw-pass"; + version = "0.1.0"; + + src = ./rbw-pass; + + nativeBuildInputs = [ + makeWrapper + ]; + + dontUnpack = true; + + dontBuild = true; + + installPhase = '' + mkdir -p $out/bin + cp $src $out/bin/${pname} + chmod a+x $out/bin/${pname} + ''; + + wrapperPath = lib.makeBinPath [ + rbw + coreutils + 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 rbw"; + homepage = "https://git.belanyi.fr/ambroisie/nix-config"; + license = with licenses; [ mit ]; + mainProgram = "rbw-pass"; + maintainers = with maintainers; [ ambroisie ]; + platforms = platforms.linux; + }; +} diff --git a/pkgs/rbw-pass/rbw-pass b/pkgs/rbw-pass/rbw-pass new file mode 100755 index 0000000..90e916c --- /dev/null +++ b/pkgs/rbw-pass/rbw-pass @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +usage() { + printf '%s\n' "Usage: bw-pass [directory name] " >&2 +} + +error_out() { + printf '%s\n' "$1" >&2 + rofi -dmenu -no-fixed-num-lines -p "$1" + exit 1 +} + +ensure_logged_in() { + rbw login +} + +query_password() { + # Either use with `query_password + # Or `query_password ` when the account has no directory + + local FOLDER_ARGS=() + local PASSWORD + + # FIXME: no way to enforce filering by "no folder" + if [ $# -eq 2 ]; then + FOLDER_ARGS+=(--folder "$1") + shift + fi + PASSWORD="$(rbw get "${FOLDER_ARGS[@]}" "$1")" + + 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/unbound-zones-adblock/default.nix b/pkgs/unbound-zones-adblock/default.nix index c4309bd..b8392ae 100644 --- a/pkgs/unbound-zones-adblock/default.nix +++ b/pkgs/unbound-zones-adblock/default.nix @@ -1,16 +1,16 @@ -{ fetchFromGitHub, gawk, lib, stdenvNoCC, unified-hosts-lists }: -stdenvNoCC.mkDerivation rec { +{ lib, gawk, stdenvNoCC, unified-hosts-lists }: +stdenvNoCC.mkDerivation { name = "unbound-zones-adblock"; version = unified-hosts-lists.version; src = unified-hosts-lists; - phases = [ "installPhase" ]; + dontUnpack = true; installPhase = let gawkCmd = lib.concatStringsSep " " [ - ''${gawk}/bin/awk'' + (lib.getExe gawk) '''{sub(/\r$/,"")}'' ''{sub(/^127\.0\.0\.1/,"0.0.0.0")}'' ''BEGIN { OFS = "" }'' @@ -32,6 +32,7 @@ stdenvNoCC.mkDerivation rec { ''; homepage = "https://github.com/StevenBlack/hosts"; license = licenses.mit; + maintainers = with maintainers; [ ambroisie ]; platforms = platforms.all; }; } diff --git a/pkgs/unified-hosts-lists/default.nix b/pkgs/unified-hosts-lists/default.nix index af55994..06d24ac 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.12.15"; src = fetchFromGitHub { owner = "StevenBlack"; repo = "hosts"; rev = version; - sha256 = "sha256-U6vRwbFSYka2VS8M1z0n+FaTkKKwdV/cCWIKxp487/I="; + hash = "sha256-HoNX57lCoIr36B/7HMuazWSWeAPPfWY1oZf6dXnxYIE="; }; - phases = [ "installPhase" ]; + dontUnpack = true; installPhase = '' mkdir -p $out @@ -28,6 +28,7 @@ stdenvNoCC.mkDerivation rec { ''; homepage = "https://github.com/StevenBlack/hosts"; license = licenses.mit; + maintainers = with maintainers; [ ambroisie ]; platforms = platforms.all; }; } diff --git a/pkgs/vimix-cursors/default.nix b/pkgs/vimix-cursors/default.nix new file mode 100644 index 0000000..80424de --- /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"; + hash = "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; + maintainers = with maintainers; [ ambroisie ]; + platforms = platforms.linux; + }; +} diff --git a/pkgs/volantes-cursors/default.nix b/pkgs/volantes-cursors/default.nix new file mode 100644 index 0000000..b2c7865 --- /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"; + hash = "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; + maintainers = with maintainers; [ ambroisie ]; + platforms = platforms.linux; + }; +} diff --git a/pkgs/wifi-qr/default.nix b/pkgs/wifi-qr/default.nix new file mode 100644 index 0000000..88164e5 --- /dev/null +++ b/pkgs/wifi-qr/default.nix @@ -0,0 +1,81 @@ +{ lib +, fetchFromGitHub +, gnome +, installShellFiles +, makeWrapper +, networkmanager +, qrencode +, stdenvNoCC +, xdg-utils +, zbar +}: +stdenvNoCC.mkDerivation rec { + pname = "wifi-qr"; + version = "unstable-2023-04-19"; + + outputs = [ "out" "man" ]; + + src = fetchFromGitHub { + owner = "kokoye2007"; + repo = "wifi-qr"; + rev = "b81d4a44257252f07e745464879aa5618ae3d434"; + hash = "sha256-oGTAr+raJGpK4PV4GdBxX8fIUE8gcbXw7W0SvQJAee0="; + }; + + nativeBuildInputs = [ + installShellFiles + makeWrapper + ]; + + dontBuild = true; + + dontConfigure = true; + + postPatch = '' + substituteInPlace wifi-qr.desktop \ + --replace "Exec=sh -c 'wifi-qr g'" "Exec=$out/bin/wifi-qr g" \ + --replace "Exec=sh -c 'wifi-qr q'" "Exec=$out/bin/wifi-qr q" \ + --replace "Exec=sh -c 'wifi-qr p'" "Exec=$out/bin/wifi-qr p" \ + --replace "Exec=sh -c 'wifi-qr c'" "Exec=$out/bin/wifi-qr c" \ + --replace "Icon=wifi-qr.svg" "Icon=wifi-qr" + ''; + + installPhase = '' + runHook preInstall + + install -Dm755 wifi-qr $out/bin/wifi-qr + + install -Dm644 wifi-qr.desktop $out/share/applications/wifi-qr.desktop + install -Dm644 wifi-qr.svg $out/share/icons/hicolor/scalable/apps/wifi-qr.svg + + installManPage wifi-qr.1 + + runHook postInstall + ''; + + wrapperPath = lib.makeBinPath [ + gnome.zenity + networkmanager + qrencode + xdg-utils + zbar + ]; + + fixupPhase = '' + runHook preFixup + + patchShebangs $out/bin/wifi-qr + wrapProgram $out/bin/wifi-qr --suffix PATH : "${wrapperPath}" + + runHook postFixup + ''; + + meta = with lib; { + description = "WiFi password sharing via QR codes"; + homepage = "https://github.com/kokoye2007/wifi-qr"; + license = with licenses; [ gpl3Plus ]; + mainProgram = "wifi-qr"; + maintainers = with maintainers; [ ambroisie ]; + platforms = platforms.linux; + }; +} 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..f7914a1 --- /dev/null +++ b/profiles/default.nix @@ -0,0 +1,13 @@ +# Configuration that spans accross system and home, or are almagations of modules +{ ... }: +{ + imports = [ + ./bluetooth + ./devices + ./gtk + ./laptop + ./printing + ./wm + ./x + ]; +} diff --git a/profiles/devices/default.nix b/profiles/devices/default.nix new file mode 100644 index 0000000..7dbd299 --- /dev/null +++ b/profiles/devices/default.nix @@ -0,0 +1,20 @@ +{ 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; + }; + + # MTP devices auto-mount via file explorers + services.gvfs.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/printing/default.nix b/profiles/printing/default.nix new file mode 100644 index 0000000..9965797 --- /dev/null +++ b/profiles/printing/default.nix @@ -0,0 +1,69 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.profiles.printing; +in +{ + options.my.profiles.printing = with lib; { + enable = mkEnableOption "printing profile"; + + papersize = mkOption { + type = with types; either str (enum [ + "a3" + "a4" + "a5" + "b5" + "letter" + "legal" + "executive" + "note" + "11x17" + ]); + default = "a4"; + example = "paper"; + description = "preferred paper size"; + }; + + usb = { + enable = my.mkDisableOption "USB printers"; + }; + + network = { + enable = my.mkDisableOption "network printers"; + }; + }; + + config = lib.mkIf cfg.enable { + # Setup CUPS + services.printing = { + enable = true; + + # Drivers are deprecated, but just in case + drivers = with pkgs; [ + gutenprint # Base set of drivers + brlaser # Brother drivers + + # Brother MFC-L3770CDW + mfcl3770cdwlpr + mfcl3770cdwcupswrapper + ]; + }; + + # Setup paper size + systemd.services.cups.serviceConfig.Environment = [ + "PAPERSIZE=${cfg.papersize}" + ]; + + # Allow using USB printers + services.ipp-usb = lib.mkIf cfg.usb.enable { + enable = true; + }; + + # Allow using WiFi printers + services.avahi = lib.mkIf cfg.network.enable { + enable = true; + openFirewall = true; + # Allow resolution of '.local' addresses + nssmdns = true; + }; + }; +} diff --git a/profiles/wm/default.nix b/profiles/wm/default.nix new file mode 100644 index 0000000..c227328 --- /dev/null +++ b/profiles/wm/default.nix @@ -0,0 +1,29 @@ +{ 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; + # udiskie fails if it can't find this dbus service + services.udisks2.enable = true; + }) + ]; +} diff --git a/profiles/x/default.nix b/profiles/x/default.nix new file mode 100644 index 0000000..ea77939 --- /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.plasma5Packages.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/default.nix b/secrets/wireguard/default.nix deleted file mode 100644 index 1dbde9f..0000000 --- a/secrets/wireguard/default.nix +++ /dev/null @@ -1,30 +0,0 @@ -{ lib, ... }: -let - peerSpec = { - # "Server" - porthos = { - clientNum = 1; - externalIp = "91.121.177.163"; - }; - - # "Clients" - aramis = { - clientNum = 2; - }; - - richelieu = { - clientNum = 3; - }; - }; - - makePeer = name: attrs: with lib; { - inherit (attrs) clientNum; - publicKey = fileContents (./. + "/${name}/public.key"); - privateKey = fileContents (./. + "/${name}/secret.key"); - } // optionalAttrs (attrs ? externalIp) { - inherit (attrs) externalIp; - }; -in -{ - peers = builtins.mapAttrs makePeer peerSpec; -} 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}"; - }; - }; -} diff --git a/services/sabnzbd.nix b/services/sabnzbd.nix deleted file mode 100644 index ebeef8b..0000000 --- a/services/sabnzbd.nix +++ /dev/null @@ -1,28 +0,0 @@ -# Usenet binary client. -{ config, lib, ... }: -let - cfg = config.my.services.sabnzbd; - - domain = config.networking.domain; - sabnzbdDomain = "sabnzbd.${domain}"; - port = 9090; # NOTE: not declaratively set... -in -{ - options.my.services.sabnzbd = with lib; { - enable = mkEnableOption "SABnzbd binary news reader"; - }; - - config = lib.mkIf cfg.enable { - services.sabnzbd = { - enable = true; - group = "media"; - }; - - services.nginx.virtualHosts."${sabnzbdDomain}" = { - forceSSL = true; - useACMEHost = domain; - - locations."/".proxyPass = "http://127.0.0.1:${toString port}"; - }; - }; -} diff --git a/templates/c++-cmake/.clang-format b/templates/c++-cmake/.clang-format new file mode 100644 index 0000000..19c58aa --- /dev/null +++ b/templates/c++-cmake/.clang-format @@ -0,0 +1,23 @@ +# vim: ft=yaml +--- +BasedOnStyle: LLVM +IndentWidth: 4 +--- +Language: Cpp +# Force pointers to the type for C++. +DerivePointerAlignment: false +PointerAlignment: Left + +# Short functions should not be on a single line, unless empty +AllowShortFunctionsOnASingleLine: Empty + +# Make them level +AccessModifierOffset: -4 + +# It makes more sense this way +BreakBeforeBinaryOperators: All +BreakBeforeTernaryOperators: true + +# Aesthetic +AlignOperands: AlignAfterOperator +--- diff --git a/templates/c++-cmake/.envrc b/templates/c++-cmake/.envrc new file mode 100644 index 0000000..95ed6fb --- /dev/null +++ b/templates/c++-cmake/.envrc @@ -0,0 +1,10 @@ +if ! has nix_direnv_version || ! nix_direnv_version 2.2.1; then + source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.2.1/direnvrc" "sha256-zelF0vLbEl5uaqrfIzbgNzJWGmLzCmYAkInj/LNxvKs=" +fi + +use flake + +watch_file ./flake/checks.nix +watch_file ./flake/dev-shells.nix + +eval "$shellHooks" diff --git a/templates/c++-cmake/.gitignore b/templates/c++-cmake/.gitignore new file mode 100644 index 0000000..09ba440 --- /dev/null +++ b/templates/c++-cmake/.gitignore @@ -0,0 +1,7 @@ +# CMake build directories +/build +/build-* + +# Nix generated files +/.pre-commit-config.yaml +/result diff --git a/templates/c++-cmake/.woodpecker/check.yml b/templates/c++-cmake/.woodpecker/check.yml new file mode 100644 index 0000000..c3b00ef --- /dev/null +++ b/templates/c++-cmake/.woodpecker/check.yml @@ -0,0 +1,26 @@ +labels: + type: exec + +pipeline: +- name: nix flake check + image: bash + commands: + - nix flake check + +- name: notifiy + image: bash + secrets: + - source: matrix_homeserver + target: address + - source: matrix_roomid + target: room + - source: matrix_username + target: user + - source: matrix_password + target: pass + commands: + - nix run '.#matrix-notifier' + when: + status: + - failure + - success diff --git a/templates/c++-cmake/CMakeLists.txt b/templates/c++-cmake/CMakeLists.txt new file mode 100644 index 0000000..122d8b3 --- /dev/null +++ b/templates/c++-cmake/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.10) +project(project VERSION 0.0.0 LANGUAGES CXX) +enable_testing() + +add_library(common_options INTERFACE) +target_compile_features(common_options INTERFACE + cxx_std_20 +) +target_compile_options(common_options INTERFACE + -Wall + -Wextra +) +target_include_directories(common_options INTERFACE + src +) + +add_subdirectory(src) +add_subdirectory(tests) diff --git a/templates/c++-cmake/flake.nix b/templates/c++-cmake/flake.nix new file mode 100644 index 0000000..cb468e7 --- /dev/null +++ b/templates/c++-cmake/flake.nix @@ -0,0 +1,112 @@ +{ + description = "A C++ project"; + + inputs = { + futils = { + type = "github"; + owner = "numtide"; + repo = "flake-utils"; + ref = "main"; + }; + + nixpkgs = { + type = "github"; + owner = "NixOS"; + repo = "nixpkgs"; + ref = "nixos-unstable"; + }; + + 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, nixpkgs, pre-commit-hooks }: + { + overlays = { + default = final: _prev: { + project = with final; stdenv.mkDerivation { + pname = "project"; + version = "0.0.0"; + + src = self; + + nativeBuildInputs = with pkgs; [ + cmake + ninja + pkg-config + ]; + + checkInputs = with pkgs; [ + gtest + ]; + + doCheck = true; + + meta = with lib; { + description = "A C++ project"; + homepage = "https://gitea.belanyi.fr/ambroisie/project"; + license = licenses.mit; + maintainers = with maintainers; [ ambroisie ]; + platforms = platforms.unix; + }; + }; + }; + }; + } // futils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { + inherit system; + overlays = [ + self.overlays.default + ]; + }; + + pre-commit = pre-commit-hooks.lib.${system}.run { + src = self; + + hooks = { + nixpkgs-fmt = { + enable = true; + }; + + clang-format = { + enable = true; + }; + }; + }; + in + { + checks = { + inherit (self.packages.${system}) project; + + inherit pre-commit; + }; + + devShells = { + default = pkgs.mkShell { + inputsFrom = with self.packages.${system}; [ + project + ]; + + packages = with pkgs; [ + clang-tools + ]; + + inherit (pre-commit) shellHook; + }; + }; + + packages = futils.lib.flattenTree { + default = pkgs.project; + inherit (pkgs) project; + }; + }); +} diff --git a/templates/c++-cmake/src/CMakeLists.txt b/templates/c++-cmake/src/CMakeLists.txt new file mode 100644 index 0000000..d91cef1 --- /dev/null +++ b/templates/c++-cmake/src/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(project main.cc) +target_link_libraries(project PRIVATE common_options) + +install(TARGETS project) diff --git a/templates/c++-cmake/src/main.cc b/templates/c++-cmake/src/main.cc new file mode 100644 index 0000000..5eb9e4a --- /dev/null +++ b/templates/c++-cmake/src/main.cc @@ -0,0 +1,5 @@ +#include + +int main() { + std::cout << "Hello World!\n"; +} diff --git a/templates/c++-cmake/tests/CMakeLists.txt b/templates/c++-cmake/tests/CMakeLists.txt new file mode 100644 index 0000000..269aea0 --- /dev/null +++ b/templates/c++-cmake/tests/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(unit) diff --git a/templates/c++-cmake/tests/unit/CMakeLists.txt b/templates/c++-cmake/tests/unit/CMakeLists.txt new file mode 100644 index 0000000..bb94448 --- /dev/null +++ b/templates/c++-cmake/tests/unit/CMakeLists.txt @@ -0,0 +1,15 @@ +find_package(GTest) + +if (${GTest_FOUND}) +include(GoogleTest) + +add_executable(dummy_test dummy_test.cc) +target_link_libraries(dummy_test PRIVATE common_options) + +target_link_libraries(dummy_test PRIVATE + GTest::gtest + GTest::gtest_main +) + +gtest_discover_tests(dummy_test) +endif (${GTest_FOUND}) diff --git a/templates/c++-cmake/tests/unit/dummy_test.cc b/templates/c++-cmake/tests/unit/dummy_test.cc new file mode 100644 index 0000000..4573678 --- /dev/null +++ b/templates/c++-cmake/tests/unit/dummy_test.cc @@ -0,0 +1,5 @@ +#include + +TEST(misc, passing) { + ASSERT_EQ(1, 1); +} diff --git a/templates/c++-meson/.clang-format b/templates/c++-meson/.clang-format new file mode 100644 index 0000000..19c58aa --- /dev/null +++ b/templates/c++-meson/.clang-format @@ -0,0 +1,23 @@ +# vim: ft=yaml +--- +BasedOnStyle: LLVM +IndentWidth: 4 +--- +Language: Cpp +# Force pointers to the type for C++. +DerivePointerAlignment: false +PointerAlignment: Left + +# Short functions should not be on a single line, unless empty +AllowShortFunctionsOnASingleLine: Empty + +# Make them level +AccessModifierOffset: -4 + +# It makes more sense this way +BreakBeforeBinaryOperators: All +BreakBeforeTernaryOperators: true + +# Aesthetic +AlignOperands: AlignAfterOperator +--- diff --git a/templates/c++-meson/.envrc b/templates/c++-meson/.envrc new file mode 100644 index 0000000..95ed6fb --- /dev/null +++ b/templates/c++-meson/.envrc @@ -0,0 +1,10 @@ +if ! has nix_direnv_version || ! nix_direnv_version 2.2.1; then + source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.2.1/direnvrc" "sha256-zelF0vLbEl5uaqrfIzbgNzJWGmLzCmYAkInj/LNxvKs=" +fi + +use flake + +watch_file ./flake/checks.nix +watch_file ./flake/dev-shells.nix + +eval "$shellHooks" diff --git a/templates/c++-meson/.gitignore b/templates/c++-meson/.gitignore new file mode 100644 index 0000000..09ba440 --- /dev/null +++ b/templates/c++-meson/.gitignore @@ -0,0 +1,7 @@ +# CMake build directories +/build +/build-* + +# Nix generated files +/.pre-commit-config.yaml +/result diff --git a/templates/c++-meson/.woodpecker/check.yml b/templates/c++-meson/.woodpecker/check.yml new file mode 100644 index 0000000..c3b00ef --- /dev/null +++ b/templates/c++-meson/.woodpecker/check.yml @@ -0,0 +1,26 @@ +labels: + type: exec + +pipeline: +- name: nix flake check + image: bash + commands: + - nix flake check + +- name: notifiy + image: bash + secrets: + - source: matrix_homeserver + target: address + - source: matrix_roomid + target: room + - source: matrix_username + target: user + - source: matrix_password + target: pass + commands: + - nix run '.#matrix-notifier' + when: + status: + - failure + - success diff --git a/templates/c++-meson/flake.nix b/templates/c++-meson/flake.nix new file mode 100644 index 0000000..9cfed0d --- /dev/null +++ b/templates/c++-meson/flake.nix @@ -0,0 +1,112 @@ +{ + description = "A C++ project"; + + inputs = { + futils = { + type = "github"; + owner = "numtide"; + repo = "flake-utils"; + ref = "main"; + }; + + nixpkgs = { + type = "github"; + owner = "NixOS"; + repo = "nixpkgs"; + ref = "nixos-unstable"; + }; + + 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, nixpkgs, pre-commit-hooks }: + { + overlays = { + default = final: _prev: { + project = with final; stdenv.mkDerivation { + pname = "project"; + version = "0.0.0"; + + src = self; + + nativeBuildInputs = with pkgs; [ + meson + ninja + pkg-config + ]; + + checkInputs = with pkgs; [ + gtest + ]; + + doCheck = true; + + meta = with lib; { + description = "A C++ project"; + homepage = "https://gitea.belanyi.fr/ambroisie/project"; + license = licenses.mit; + maintainers = with maintainers; [ ambroisie ]; + platforms = platforms.unix; + }; + }; + }; + }; + } // futils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { + inherit system; + overlays = [ + self.overlays.default + ]; + }; + + pre-commit = pre-commit-hooks.lib.${system}.run { + src = self; + + hooks = { + nixpkgs-fmt = { + enable = true; + }; + + clang-format = { + enable = true; + }; + }; + }; + in + { + checks = { + inherit (self.packages.${system}) project; + + inherit pre-commit; + }; + + devShells = { + default = pkgs.mkShell { + inputsFrom = with self.packages.${system}; [ + project + ]; + + packages = with pkgs; [ + clang-tools + ]; + + inherit (pre-commit) shellHook; + }; + }; + + packages = futils.lib.flattenTree { + default = pkgs.project; + inherit (pkgs) project; + }; + }); +} diff --git a/templates/c++-meson/meson.build b/templates/c++-meson/meson.build new file mode 100644 index 0000000..bc228c6 --- /dev/null +++ b/templates/c++-meson/meson.build @@ -0,0 +1,13 @@ +project( + 'project', + 'cpp', + version : '0.0.0', + license: 'MIT', + default_options : [ + 'cpp_std=c++20', + 'warning_level=3', + ], +) + +subdir('src') +subdir('tests') diff --git a/templates/c++-meson/src/main.cc b/templates/c++-meson/src/main.cc new file mode 100644 index 0000000..5eb9e4a --- /dev/null +++ b/templates/c++-meson/src/main.cc @@ -0,0 +1,5 @@ +#include + +int main() { + std::cout << "Hello World!\n"; +} diff --git a/templates/c++-meson/src/meson.build b/templates/c++-meson/src/meson.build new file mode 100644 index 0000000..4b2ff23 --- /dev/null +++ b/templates/c++-meson/src/meson.build @@ -0,0 +1,8 @@ +sources = files( + 'main.cc', +) + +executable( + 'project', + sources : sources, +) diff --git a/templates/c++-meson/tests/meson.build b/templates/c++-meson/tests/meson.build new file mode 100644 index 0000000..082b746 --- /dev/null +++ b/templates/c++-meson/tests/meson.build @@ -0,0 +1 @@ +subdir('unit') diff --git a/templates/c++-meson/tests/unit/dummy_test.cc b/templates/c++-meson/tests/unit/dummy_test.cc new file mode 100644 index 0000000..4573678 --- /dev/null +++ b/templates/c++-meson/tests/unit/dummy_test.cc @@ -0,0 +1,5 @@ +#include + +TEST(misc, passing) { + ASSERT_EQ(1, 1); +} diff --git a/templates/c++-meson/tests/unit/meson.build b/templates/c++-meson/tests/unit/meson.build new file mode 100644 index 0000000..44c8a92 --- /dev/null +++ b/templates/c++-meson/tests/unit/meson.build @@ -0,0 +1,13 @@ +gtest_dep = dependency( + 'gtest', + main : true, + required : false, +) + +dummy_test = executable( + 'dummy_test', + 'dummy_test.cc', + dependencies : gtest_dep, +) + +test('dummy test', dummy_test) diff --git a/templates/default.nix b/templates/default.nix new file mode 100644 index 0000000..f58fd72 --- /dev/null +++ b/templates/default.nix @@ -0,0 +1,10 @@ +{ + "c++-cmake" = { + path = ./c++-cmake; + description = "A C++ project using CMake"; + }; + "c++-meson" = { + path = ./c++-meson; + description = "A C++ project using CMake"; + }; +}