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..a6b1f81 100644 --- a/.envrc +++ b/.envrc @@ -1,8 +1,9 @@ -use_flake() { - watch_file flake.nix - watch_file flake.lock - eval "$(nix print-dev-env)" -} +# shellcheck shell=bash +if ! has nix_direnv_version || ! nix_direnv_version 3.0.0; then + source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.0/direnvrc" "sha256-21TMnI2xWX7HkSTjFFri2UaohXVj854mgvWapWrxRXg=" +fi + +watch_file ./flake/checks.nix +watch_file ./flake/dev-shells.nix -ulimit -s unlimited # Bypass current bug in `nix` flakes evaluation use flake 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..9e885aa --- /dev/null +++ b/.woodpecker/check.yml @@ -0,0 +1,26 @@ +labels: + backend: local + +steps: +- name: nix flake check + image: bash + commands: + - nix flake check + +- name: notify + image: bash + environment: + ADDRESS: + from_secret: matrix_homeserver + ROOM: + from_secret: matrix_roomid + USER: + from_secret: matrix_username + PASS: + from_secret: matrix_password + 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..db1662e 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -1,5 +1,6 @@ #!/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 +# shellcheck shell=bash # Command failure is script failure set -e @@ -10,7 +11,6 @@ BOLD_GREEN="\e[0;1;32m" RESET="\e[0m" -DEST="$HOME/.config/nixpkgs" BW_SESSION="" warn() { @@ -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..e96a7a0 100644 --- a/flake.lock +++ b/flake.lock @@ -1,21 +1,157 @@ { "nodes": { - "futils": { + "agenix": { + "inputs": { + "darwin": "darwin", + "home-manager": [ + "home-manager" + ], + "nixpkgs": [ + "nixpkgs" + ], + "systems": [ + "systems" + ] + }, "locked": { - "lastModified": 1619345332, - "narHash": "sha256-qHnQkEp1uklKTpx3MvKtY6xzgcqXDsz5nLilbbuL+3A=", + "lastModified": 1750173260, + "narHash": "sha256-9P1FziAwl5+3edkfFcr5HeGtQUtrSdk/MksX39GieoA=", + "owner": "ryantm", + "repo": "agenix", + "rev": "531beac616433bac6f9e2a19feb8e99a22a66baf", + "type": "github" + }, + "original": { + "owner": "ryantm", + "ref": "main", + "repo": "agenix", + "type": "github" + } + }, + "darwin": { + "inputs": { + "nixpkgs": [ + "agenix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1744478979, + "narHash": "sha256-dyN+teG9G82G+m+PX/aSAagkC+vUv0SgUw3XkPhQodQ=", + "owner": "lnl7", + "repo": "nix-darwin", + "rev": "43975d782b418ebf4969e9ccba82466728c2851b", + "type": "github" + }, + "original": { + "owner": "lnl7", + "ref": "master", + "repo": "nix-darwin", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1753121425, + "narHash": "sha256-TVcTNvOeWWk1DXljFxVRp+E0tzG1LhrVjOGGoMHuXio=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "644e0fc48951a860279da645ba77fe4a6e814c5e", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "ref": "main", + "repo": "flake-parts", + "type": "github" + } + }, + "futils": { + "inputs": { + "systems": [ + "systems" + ] + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", "owner": "numtide", "repo": "flake-utils", - "rev": "2ebf2558e5bf978c7fb8ea927dfaed8fefab2e28", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", "type": "github" }, "original": { "owner": "numtide", - "ref": "master", + "ref": "main", "repo": "flake-utils", "type": "github" } }, + "git-hooks": { + "inputs": { + "flake-compat": "flake-compat", + "gitignore": "gitignore", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1750779888, + "narHash": "sha256-wibppH3g/E2lxU43ZQHC5yA/7kIKLGxVEnsnVK1BtRg=", + "owner": "cachix", + "repo": "git-hooks.nix", + "rev": "16ec914f6fb6f599ce988427d9d94efddf25fe6d", + "type": "github" + }, + "original": { + "owner": "cachix", + "ref": "master", + "repo": "git-hooks.nix", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "git-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709087332, + "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, "home-manager": { "inputs": { "nixpkgs": [ @@ -23,11 +159,11 @@ ] }, "locked": { - "lastModified": 1619558193, - "narHash": "sha256-DljP5/9EX0eXEPhzCUFqFEHkkcFuXJBx1PTgcv0OgyM=", + "lastModified": 1753617834, + "narHash": "sha256-WEVfKrdIdu5CpppJ0Va3vzP0DKlS+ZTLbBjugMO2Drg=", "owner": "nix-community", "repo": "home-manager", - "rev": "18ad12d52b8cebbb57013865eec2be5125de050a", + "rev": "72cc1e3134a35005006f06640724319caa424737", "type": "github" }, "original": { @@ -39,11 +175,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1619464443, - "narHash": "sha256-R7WAb8EnkIJxxaF6GTHUPytjonhB4Zm0iatyWoW169A=", + "lastModified": 1753429684, + "narHash": "sha256-9h7+4/53cSfQ/uA3pSvCaBepmZaz/dLlLVJnbQ+SJjk=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "8e4fe32876ca15e3d5eb3ecd3ca0b224417f5f17", + "rev": "7fd36ee82c0275fb545775cc5e4d30542899511d", "type": "github" }, "original": { @@ -54,12 +190,21 @@ } }, "nur": { + "inputs": { + "flake-parts": [ + "flake-parts" + ], + "nixpkgs": [ + "nixpkgs" + ], + "treefmt-nix": "treefmt-nix" + }, "locked": { - "lastModified": 1619628114, - "narHash": "sha256-s3pQyvMfXVmbQOX224yOWQf6zi8406sShFF4u17LVQ0=", + "lastModified": 1741294988, + "narHash": "sha256-3408u6q615kVTb23WtDriHRmCBBpwX7iau6rvfipcu4=", "owner": "nix-community", "repo": "NUR", - "rev": "0615e756dc14986c4968fa478c0bd080d621cb2b", + "rev": "b30c245e2c44c7352a27485bfd5bc483df660f0e", "type": "github" }, "original": { @@ -71,10 +216,51 @@ }, "root": { "inputs": { + "agenix": "agenix", + "flake-parts": "flake-parts", "futils": "futils", + "git-hooks": "git-hooks", "home-manager": "home-manager", "nixpkgs": "nixpkgs", - "nur": "nur" + "nur": "nur", + "systems": "systems" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "ref": "main", + "repo": "default", + "type": "github" + } + }, + "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "nur", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1733222881, + "narHash": "sha256-JIPcz1PrpXUCbaccEnrcUS8jjEb/1vJbZz5KkobyFdM=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "49717b5af6f80172275d47a418c9719a31a78b53", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" } } }, diff --git a/flake.nix b/flake.nix index f582719..5076729 100644 --- a/flake.nix +++ b/flake.nix @@ -1,11 +1,36 @@ { description = "NixOS configuration with flakes"; inputs = { + agenix = { + type = "github"; + owner = "ryantm"; + repo = "agenix"; + ref = "main"; + inputs = { + home-manager.follows = "home-manager"; + nixpkgs.follows = "nixpkgs"; + systems.follows = "systems"; + }; + }; + + 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"; + inputs = { + systems.follows = "systems"; + }; }; home-manager = { @@ -30,80 +55,30 @@ owner = "nix-community"; repo = "NUR"; ref = "master"; + inputs = { + flake-parts.follows = "flake-parts"; + nixpkgs.follows = "nixpkgs"; + }; + }; + + git-hooks = { + type = "github"; + owner = "cachix"; + repo = "git-hooks.nix"; + ref = "master"; + inputs = { + nixpkgs.follows = "nixpkgs"; + }; + }; + + systems = { + type = "github"; + owner = "nix-systems"; + repo = "default"; + ref = "main"; }; }; - 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"; - }; - }; + # 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..73e64d5 --- /dev/null +++ b/flake/checks.nix @@ -0,0 +1,33 @@ +{ inputs, ... }: +{ + imports = [ + inputs.git-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..5e52025 --- /dev/null +++ b/flake/default.nix @@ -0,0 +1,22 @@ +{ flake-parts +, systems +, ... +} @ inputs: +let + mySystems = import systems; +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..87464a4 --- /dev/null +++ b/flake/dev-shells.nix @@ -0,0 +1,18 @@ +{ ... }: +{ + perSystem = { config, pkgs, ... }: { + devShells = { + default = pkgs.mkShellNoCC { + name = "NixOS-config"; + + nativeBuildInputs = with pkgs; [ + 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..88a74e8 --- /dev/null +++ b/flake/home-manager.nix @@ -0,0 +1,60 @@ +{ self, inputs, lib, ... }: +let + defaultModules = [ + # Include generic settings + "${self}/modules/home" + { + nixpkgs.overlays = (lib.attrValues self.overlays) ++ [ + inputs.nur.overlays.default + ]; + } + { + # 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 { + pkgs = inputs.nixpkgs.legacyPackages.${system}; + + modules = defaultModules ++ [ + "${self}/hosts/homes/${name}" + ]; + + # Use my extended lib in NixOS configuration + inherit (self) lib; + + extraSpecialArgs = { + # Inject inputs to use them in global registry + inherit inputs; + }; + }; + + homes = { + "ambroisie@bazin" = "x86_64-linux"; + "ambroisie@mousqueton" = "x86_64-linux"; + }; +in +{ + perSystem = { system, ... }: { + # Work-around for https://github.com/nix-community/home-manager/issues/3075 + legacyPackages = { + homeConfigurations = + let + filteredHomes = lib.filterAttrs (_: v: v == system) homes; + allHomes = filteredHomes // { + # Default configuration + ambroisie = system; + }; + in + lib.mapAttrs mkHome allHomes; + }; + }; +} 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..0fbd3a6 --- /dev/null +++ b/flake/nixos.nix @@ -0,0 +1,37 @@ +{ self, inputs, lib, ... }: +let + defaultModules = [ + { + # Let 'nixos-version --json' know about the Git revision + system.configurationRevision = self.rev or self.dirtyRev or "dirty"; + } + { + nixpkgs.overlays = (lib.attrValues self.overlays) ++ [ + inputs.nur.overlays.default + ]; + } + # Include generic settings + "${self}/modules/nixos" + ]; + + buildHost = name: system: lib.nixosSystem { + modules = defaultModules ++ [ + { + nixpkgs.hostPlatform = system; + } + "${self}/hosts/nixos/${name}" + ]; + specialArgs = { + # Use my extended lib in NixOS configuration + inherit (self) 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/default.nix b/home/default.nix deleted file mode 100644 index e8218e8..0000000 --- a/home/default.nix +++ /dev/null @@ -1,26 +0,0 @@ -{ ... }: -{ - imports = [ - ./bat.nix - ./direnv.nix - ./documentation.nix - ./git - ./gpg.nix - ./htop.nix - ./jq.nix - ./packages.nix - ./pager.nix - ./secrets # Home-manager specific secrets - ./ssh.nix - ./tmux.nix - ./vim - ./xdg.nix - ./zsh - ]; - - # First sane reproducible version - home.stateVersion = "20.09"; - - # Who am I? - home.username = "ambroisie"; -} 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/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/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/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/secrets/.gitattributes b/home/secrets/.gitattributes deleted file mode 100644 index a741d4d..0000000 --- a/home/secrets/.gitattributes +++ /dev/null @@ -1,3 +0,0 @@ -* filter=git-crypt diff=git-crypt -.gitattributes !filter !diff -/default.nix !filter !diff diff --git a/home/secrets/canary b/home/secrets/canary deleted file mode 100644 index e910ea3..0000000 Binary files a/home/secrets/canary and /dev/null differ diff --git a/home/secrets/default.nix b/home/secrets/default.nix deleted file mode 100644 index 3624472..0000000 --- a/home/secrets/default.nix +++ /dev/null @@ -1,19 +0,0 @@ -{ lib, ... }: - -with lib; -let - canaryHash = builtins.hashFile "sha256" ./canary; - expectedHash = - "9df8c065663197b5a1095122d48e140d3677d860343256abd5ab6e4fb4c696ab"; -in -if canaryHash != expectedHash then - abort "Secrets are not readable. Have you run `git-crypt unlock`?" -else { - options.my.secrets = mkOption { - type = types.attrs; - }; - - config.my.secrets = { - # Home-manager secrets go here - }; -} diff --git a/home/ssh.nix b/home/ssh.nix deleted file mode 100644 index 22f85d3..0000000 --- a/home/ssh.nix +++ /dev/null @@ -1,49 +0,0 @@ -{ config, lib, ... }: -let - cfg = config.my.home.ssh; -in -{ - options.my.home.ssh = with lib.my; { - enable = mkDisableOption "ssh configuration"; - }; - - config.programs.ssh = { - enable = true; - - matchBlocks = { - "github.com" = { - hostname = "github.com"; - identityFile = "~/.ssh/shared_rsa"; - user = "git"; - }; - - "gitlab.com" = { - hostname = "gitlab.com"; - identityFile = "~/.ssh/shared_rsa"; - user = "git"; - }; - - "git.sr.ht" = { - hostname = "git.sr.ht"; - identityFile = "~/.ssh/shared_rsa"; - user = "git"; - }; - - "gitea.belanyi.fr" = { - hostname = "gitea.belanyi.fr"; - identityFile = "~/.ssh/shared_rsa"; - user = "git"; - }; - - porthos = { - hostname = "91.121.177.163"; - identityFile = "~/.ssh/shared_rsa"; - user = "ambroisie"; - }; - }; - - extraConfig = '' - AddKeysToAgent yes - ''; - }; -} 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/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/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 deleted file mode 100644 index 80d7f2e..0000000 --- a/home/vim/after/ftplugin/haskell.vim +++ /dev/null @@ -1,30 +0,0 @@ -" Create the `b:undo_ftplugin` variable if it doesn't exist -call ftplugined#check_undo_ft() - -" Use a small indentation value on Haskell files -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/netrw.vim b/home/vim/after/ftplugin/netrw.vim deleted file mode 100644 index e3689f8..0000000 --- a/home/vim/after/ftplugin/netrw.vim +++ /dev/null @@ -1,6 +0,0 @@ -" Create the `b:undo_ftplugin` variable if it doesn't exist -call ftplugined#check_undo_ft() - -" Don't show Netrw in buffer list -setlocal bufhidden=delete -let b:undo_ftplugin='|setlocal bufhidden<' 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 deleted file mode 100644 index 61516f9..0000000 --- a/home/vim/after/ftplugin/rust.vim +++ /dev/null @@ -1,31 +0,0 @@ -" 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/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/fugitive.vim b/home/vim/after/plugin/mappings/fugitive.vim deleted file mode 100644 index 934f00e..0000000 --- a/home/vim/after/plugin/mappings/fugitive.vim +++ /dev/null @@ -1,10 +0,0 @@ -" Visual bindings for merging diffs as in normal mode -xnoremap dp :diffput -xnoremap do :diffget - -" Open status window -nnoremap gs :Gstatus -" Open diff view of current buffer: the up/left window is the current index -nnoremap gd :Gdiffsplit! -" Open current file log in new tab, populate its location list with history -nnoremap gl :spT:Gllog --follow -- %:p diff --git a/home/vim/after/plugin/mappings/fzf.vim b/home/vim/after/plugin/mappings/fzf.vim deleted file mode 100644 index 8cfb8b8..0000000 --- a/home/vim/after/plugin/mappings/fzf.vim +++ /dev/null @@ -1,38 +0,0 @@ -" Only git-tracked files, Vim needs to be in a Git repository -nnoremap fg :GFiles - -" All files -nnoremap ff :Files - -" Currently open buffers -nnoremap fb :Buffers - -" Buffer history -nnoremap fh :History - -" Tags in buffer -nnoremap ft :BTags - -" Tags in all project files -nnoremap fT :Tags - -" Snippets for the current fileytpe (using Ultisnips) -nnoremap fs :Snippets - -" All available commands -nnoremap f: :Commands - -" All commits (using fugitive) -nnoremap fc :Commits - -" All commits for the current buffer (using fugitive) -nnoremap fC :BCommits - -" Select normal mode mapping by searching for its name -nmap (fzf-maps-n) - -" Select visual mode mapping by searching for its name -xmap (fzf-maps-x) - -" Select operator pending mode mapping by searching for its name -omap (fzf-maps-o) diff --git a/home/vim/after/plugin/mappings/misc.vim b/home/vim/after/plugin/mappings/misc.vim 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/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 deleted file mode 100644 index 08f5ecd..0000000 --- a/home/vim/default.nix +++ /dev/null @@ -1,77 +0,0 @@ -{ config, pkgs, lib, ... }: -let - cfg = config.my.home.vim; - configFiles = - let - toSource = directory: { source = ./. + "/${directory}"; }; - configureDirectory = - name: lib.nameValuePair "nvim/${name}" (toSource name); - linkDirectories = - dirs: builtins.listToAttrs (map configureDirectory dirs); - in - linkDirectories [ - "after" - "autoload" - "ftdetect" - "plugin" - ]; -in -{ - options.my.home.vim = with lib.my; { - enable = mkDisableOption "vim configuration"; - }; - - config.programs.neovim = lib.mkIf cfg.enable { - enable = true; - # All the aliases - viAlias = true; - vimAlias = true; - vimdiffAlias = true; - - plugins = with pkgs.vimPlugins; [ - # Theming - lightline-vim # Fancy status bar - { - plugin = onedark-vim; # Nice dark theme - optional = true; # Needs to be `packadd`-ed manually... - } - - # tpope essentials - vim-commentary # Easy comments - vim-eunuch # UNIX integrations - vim-fugitive # A 'git' wrapper - 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 - - # Languages - rust-vim - vim-beancount - vim-jsonnet - vim-nix - vim-pandoc - vim-pandoc-syntax - vim-toml - - # General enhancements - fastfold # Better folding - vim-qf # Better quick-fix list - - # Other wrappers - fzfWrapper # The vim plugin inside the 'fzf' package - fzf-vim # Fuzzy commands - git-messenger-vim # A simple blame window - - # LSP and linting - ale # Asynchronous Linting Engine - lightline-ale # Status bar integration - ]; - - extraConfig = builtins.readFile ./init.vim; - }; - - config.xdg.configFile = lib.mkIf cfg.enable configFiles; -} 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/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/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.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/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/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/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/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/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/zsh/default.nix b/home/zsh/default.nix deleted file mode 100644 index b7b9e8e..0000000 --- a/home/zsh/default.nix +++ /dev/null @@ -1,79 +0,0 @@ -{ config, pkgs, lib, ... }: -let - cfg = config.my.home.zsh; -in -{ - options.my.home.zsh = with lib.my; { - enable = mkDisableOption "zsh configuration"; - }; - - config.programs.zsh = lib.mkIf cfg.enable { - enable = true; - dotDir = ".config/zsh"; # Don't clutter $HOME - enableCompletion = true; - - history = { - size = 50000; - ignoreSpace = true; - ignoreDups = true; - share = true; - path = "${config.xdg.dataHome}/zsh/zsh_history"; - }; - - plugins = with pkgs; [ - { - name = "fast-syntax-highlighting"; - src = fetchFromGitHub { - owner = "zdharma"; - repo = "fast-syntax-highlighting"; - rev = "v1.55"; - sha256 = "sha256-DWVFBoICroKaKgByLmDEo4O+xo6eA8YO792g8t8R7kA="; - }; - } - { - name = "agkozak-zsh-prompt"; - src = fetchFromGitHub { - owner = "agkozak"; - repo = "agkozak-zsh-prompt"; - rev = "v3.9.0"; - sha256 = "sha256-VTRL+8ph2eI7iPht15epkLggAgtLGxB3DORFTW5GrhE="; - }; - } - ]; - - # Modal editing is life, but CLI benefits from emacs gymnastics - defaultKeymap = "emacs"; - - initExtra = lib.concatMapStrings builtins.readFile [ - ./completion-styles.zsh - ./extra-mappings.zsh - ./options.zsh - ]; - - localVariables = { - # I like having the full path - AGKOZAK_PROMPT_DIRTRIM = 0; - # Because I *am* from EPITA - AGKOZAK_PROMPT_CHAR = [ "42sh$" "42sh#" ":" ]; - # Easy on the eyes - AGKOZAK_COLORS_BRANCH_STATUS = "magenta"; - # I don't like moving my eyes - AGKOZAK_LEFT_PROMPT_ONLY = 1; - }; - - shellAliases = { - # Sometime `gpg-agent` errors out... - reset-agent = "gpg-connect-agent updatestartuptty /bye"; - }; - }; - - # 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 deleted file mode 100644 index abd6e58..0000000 --- a/home/zsh/extra-mappings.zsh +++ /dev/null @@ -1,14 +0,0 @@ -# Fix delete key not working -bindkey "\e[3~" delete-char - -# Fix Ctrl+u killing from the cursor instead of the whole 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 -zle -N edit-command-line -bindkey '^xe' edit-command-line -bindkey '^x^e' edit-command-line - -# Enable Shift-Tab to go backwards in completion list -bindkey '^[[Z' reverse-menu-complete diff --git a/home/zsh/options.zsh b/home/zsh/options.zsh deleted file mode 100644 index b02ca54..0000000 --- a/home/zsh/options.zsh +++ /dev/null @@ -1,12 +0,0 @@ -# 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 -# Use pushd when cd-ing around -setopt autopushd pushdminus pushdsilent -# Use single quotes in string without the weird escape tricks -setopt rcquotes -# Single word commands can resume an existing job -setopt autoresume -# Those options aren't wanted -unsetopt beep extendedglob 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@bazin/default.nix b/hosts/homes/ambroisie@bazin/default.nix new file mode 100644 index 0000000..365b70d --- /dev/null +++ b/hosts/homes/ambroisie@bazin/default.nix @@ -0,0 +1,57 @@ +# Google Laptop configuration +{ lib, options, pkgs, ... }: +{ + services.gpg-agent.enable = lib.mkForce false; + + my.home = { + atuin = { + package = pkgs.stdenv.mkDerivation { + pname = "atuin"; + version = "18.4.0"; + + buildCommand = '' + mkdir -p $out/bin + ln -s /usr/bin/atuin $out/bin/atuin + ''; + + meta.mainProgram = "atuin"; + }; + }; + + git = { + package = pkgs.emptyDirectory; + }; + + tmux = { + # I use scripts that use the passthrough sequence often on this host + enablePassthrough = true; + + terminalFeatures = { + # HTerm uses `xterm-256color` as its `$TERM`, so use that here + xterm-256color = { }; + }; + }; + + ssh = { + mosh = { + package = pkgs.emptyDirectory; + }; + }; + + zsh = { + notify = { + enable = true; + + exclude = options.my.home.zsh.notify.exclude.default ++ [ + "adb shell$" # Only interactive shell sessions + ]; + + ssh = { + enable = true; + # `notify-send` is proxied to the ChromeOS layer + useOsc777 = false; + }; + }; + }; + }; +} diff --git a/hosts/homes/ambroisie@mousqueton/default.nix b/hosts/homes/ambroisie@mousqueton/default.nix new file mode 100644 index 0000000..1383618 --- /dev/null +++ b/hosts/homes/ambroisie@mousqueton/default.nix @@ -0,0 +1,41 @@ +# Google Cloudtop configuration +{ lib, pkgs, ... }: +{ + # Google specific configuration + home.homeDirectory = "/usr/local/google/home/ambroisie"; + + services.gpg-agent.enable = lib.mkForce false; + + my.home = { + atuin = { + package = pkgs.stdenv.mkDerivation { + pname = "atuin"; + version = "18.4.0"; + + buildCommand = '' + mkdir -p $out/bin + ln -s /usr/bin/atuin $out/bin/atuin + ''; + + meta.mainProgram = "atuin"; + }; + }; + + git = { + package = pkgs.emptyDirectory; + }; + + tmux = { + # I use scripts that use the passthrough sequence often on this host + enablePassthrough = true; + + # Frequent reboots mean that session persistence can be handy + enableResurrect = true; + + terminalFeatures = { + # HTerm uses `xterm-256color` as its `$TERM`, so use that here + xterm-256color = { }; + }; + }; + }; +} 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/porthos.nix b/hosts/nixos/aramis/default.nix similarity index 56% rename from porthos.nix rename to hosts/nixos/aramis/default.nix index ce3a200..6e0304f 100644 --- a/porthos.nix +++ b/hosts/nixos/aramis/default.nix @@ -1,18 +1,26 @@ -# Porthos self-hosted server +# Edit this configuration file to define what should be installed on +# your system. Help is available in the configuration.nix(5) man page +# and in the NixOS manual (accessible by running ‘nixos-help’). + { ... }: { imports = [ - # Include generic settings - ./modules - # Include porthos-specific modules - ./machines/porthos - # Include my secrets + ./boot.nix + ./hardware.nix + ./home.nix + ./networking.nix + ./profiles.nix + ./programs.nix ./secrets - # Include my services - ./services + ./services.nix + ./sound.nix + ./system.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 diff --git a/hosts/nixos/aramis/hardware.nix b/hosts/nixos/aramis/hardware.nix new file mode 100644 index 0000000..99bc77e --- /dev/null +++ b/hosts/nixos/aramis/hardware.nix @@ -0,0 +1,46 @@ +{ 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"; + }; + + graphics = { + enable = true; + + gpuFlavor = "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..221b1ea --- /dev/null +++ b/hosts/nixos/aramis/home.nix @@ -0,0 +1,34 @@ +{ pkgs, ... }: +{ + my.home = { + # Use graphical pinentry + bitwarden.pinentry = pkgs.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 = pkgs.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 + trgui-ng # 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..4d2ac7d --- /dev/null +++ b/hosts/nixos/aramis/profiles.nix @@ -0,0 +1,17 @@ +{ ... }: +{ + my.profiles = { + # Bluetooth configuration and GUI + bluetooth.enable = true; + # Mouse and keyboard configuration + devices.enable = true; + # GTK theme configuration + gtk.enable = true; + # Laptop specific configuration + laptop.enable = true; + # i3 configuration + wm.windowManager = "i3"; + # X configuration + x.enable = true; + }; +} diff --git a/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/hosts/nixos/aramis/system.nix b/hosts/nixos/aramis/system.nix new file mode 100644 index 0000000..5e69222 --- /dev/null +++ b/hosts/nixos/aramis/system.nix @@ -0,0 +1,10 @@ +# Core system configuration +{ ... }: +{ + my.system = { + # Printers are hell, but so is the unability to print + printing = { + enable = true; + }; + }; +} diff --git a/hosts/nixos/porthos/boot.nix b/hosts/nixos/porthos/boot.nix new file mode 100644 index 0000000..461e969 --- /dev/null +++ b/hosts/nixos/porthos/boot.nix @@ -0,0 +1,21 @@ +# Boot configuration +{ ... }: + +{ + boot = { + # Use the systemd-boot EFI boot loader. + loader = { + systemd-boot.enable = true; + efi.canTouchEfiVariables = true; + }; + + initrd = { + availableKernelModules = [ "ahci" "xhci_pci" "ehci_pci" "usbhid" "sd_mod" ]; + kernelModules = [ "dm-snapshot" ]; + }; + + kernelModules = [ "kvm-intel" ]; + + extraModulePackages = [ ]; + }; +} diff --git a/hosts/nixos/porthos/default.nix b/hosts/nixos/porthos/default.nix new file mode 100644 index 0000000..bd1bdb1 --- /dev/null +++ b/hosts/nixos/porthos/default.nix @@ -0,0 +1,20 @@ +# Porthos specific settings +{ ... }: + +{ + imports = [ + ./boot.nix + ./hardware.nix + ./home.nix + ./networking.nix + ./secrets + ./services.nix + ./system.nix + ./users.nix + ]; + + # Set your time zone. + time.timeZone = "Europe/Paris"; + + system.stateVersion = "24.05"; # Did you read the comment? +} diff --git a/machines/porthos/hardware.nix b/hosts/nixos/porthos/hardware.nix similarity index 58% rename from machines/porthos/hardware.nix rename to hosts/nixos/porthos/hardware.nix index 5a6e0d7..2172c5c 100644 --- a/machines/porthos/hardware.nix +++ b/hosts/nixos/porthos/hardware.nix @@ -1,5 +1,5 @@ # Hardware configuration -{ lib, modulesPath, ... }: +{ modulesPath, ... }: { imports = [ @@ -11,9 +11,18 @@ fsType = "ext4"; }; + fileSystems."/boot" = { + device = "/dev/disk/by-label/boot"; + fsType = "vfat"; + }; + swapDevices = [ { device = "/dev/disk/by-label/swap"; } ]; - powerManagement.cpuFreqGovernor = lib.mkDefault "ondemand"; + my.hardware = { + firmware = { + cpuFlavor = "intel"; + }; + }; } diff --git a/hosts/nixos/porthos/home.nix b/hosts/nixos/porthos/home.nix new file mode 100644 index 0000000..c2c858b --- /dev/null +++ b/hosts/nixos/porthos/home.nix @@ -0,0 +1,20 @@ +{ ... }: +{ + my.home = { + nix = { + cache = { + # This server is the one serving the cache, don't try to query it + selfHosted = false; + }; + }; + + # Allow using extended features when SSH-ing from various clients + tmux.terminalFeatures = { + # My usual terminal, e.g: on laptop + alacritty = { }; + }; + + # 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 56% rename from machines/porthos/install.sh rename to hosts/nixos/porthos/install.sh index 44ea787..e6ba0aa 100644 --- a/machines/porthos/install.sh +++ b/hosts/nixos/porthos/install.sh @@ -3,7 +3,7 @@ SWAP_SIZE=16GiB parted /dev/sda --script -- \ - mklabel msdos \ + mklabel gpt \ mkpart primary 512MiB -$SWAP_SIZE \ mkpart primary linux-swap -$SWAP_SIZE 100% \ mkpart ESP fat32 1MiB 512MiB \ @@ -11,14 +11,24 @@ parted /dev/sda --script -- \ parted /dev/sdb --script -- \ mklabel gpt \ - mkpart primary 0MiB 100% + mkpart primary 0% 100% +parted /dev/sdc --script -- \ + mklabel gpt \ + mkpart primary 0% 100% +parted /dev/sdd --script -- \ + mklabel gpt \ + mkpart primary 0% 100% mkfs.ext4 -L media1 /dev/sda1 mkfs.ext4 -L media2 /dev/sdb1 +mkfs.ext4 -L media3 /dev/sdc1 +mkfs.ext4 -L media4 /dev/sdd1 pvcreate /dev/sda1 pvcreate /dev/sdb1 -vgcreate lvm /dev/sda1 /dev/sdb1 +pvcreate /dev/sdc1 +pvcreate /dev/sdd1 +vgcreate lvm /dev/sda1 /dev/sdb1 /dev/sdc1 /dev/sdd1 lvcreate -l 100%FREE -n media lvm mkfs.ext4 -L nixos /dev/mapper/lvm-media @@ -27,17 +37,18 @@ mkfs.fat -F 32 -n boot /dev/sda3 mount /dev/disk/by-label/nixos /mnt swapon /dev/sda2 +mkdir -p /mnt/boot +mount /dev/disk/by-label/boot /mnt/boot apt install sudo useradd -m -G sudo setupuser -su setupuser cat << EOF # Run the following commands as setup user -curl -L https://nixos.org/nix/install | sh -. $HOME/.nix-profile/etc/profile.d/nix.sh -nix-channel --add https://nixos.org/channels/nixos-20.09 nixpkgs -sudo `which nixos-generate-config` --root /mnt +curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install +. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh +nix profile install nixpkgs#nixos-install-tools +sudo "$(which nixos-generate-config)" --root /mnt # Change uuids to labels vim /mnt/etc/nixos/hardware-configuration.nix @@ -46,10 +57,13 @@ 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 nixos-install --root /mnt --flake '.#' EOF + +# shellcheck disable=2117 +su setupuser diff --git a/hosts/nixos/porthos/networking.nix b/hosts/nixos/porthos/networking.nix new file mode 100644 index 0000000..717652b --- /dev/null +++ b/hosts/nixos/porthos/networking.nix @@ -0,0 +1,22 @@ +# Networking configuration +{ ... }: + +{ + networking = { + hostName = "porthos"; # Define your hostname. + domain = "belanyi.fr"; # Define your domain. + + # Enables DHCP on each ethernet and wireless interface. In case of scripted networking + # (the default) this is the recommended approach. When using systemd-networkd it's + # still possible to use this option, but it's recommended to use it in conjunction + # with explicit per-interface declarations with `networking.interfaces..useDHCP`. + useDHCP = true; + interfaces = { + eno1.useDHCP = true; + eno2.useDHCP = true; + }; + }; + + # Which interface is used to connect to the internet + my.hardware.networking.externalInterface = "eno1"; +} 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..d7f159e --- /dev/null +++ b/hosts/nixos/porthos/secrets/acme/dns-key.age @@ -0,0 +1,9 @@ +age-encryption.org/v1 +-> ssh-ed25519 cKojmg Ec0xt1uJTva8MxUdoTVX5m3uWaIiRlodf345FEM7Uzs +aJIneWFJPB5HVeoUGp57agXih9YeZ6xMEbyQ+zJtWQY +-> ssh-ed25519 jPowng B5XotRgv7s/FUegGhceBj7EoukewNUOIFl4TFRQf1EQ +PgGCBd/Pqwp7ayqi7okHBGF1SfFpwT4KlHJ/np6p2uQ +--- AeLgwGz6k3OABb53cXNaCU/sgI4FlU1s6p8PhAaFOlg +1CԹULfI1Hmb}m šg0`XG>\>8rz+Y`ʢ.JBU!z¸Z50*ٟI] I +ĵo۰g¿tncz[{ +j&NNo{ -eP=L 6.SP:e \ No newline at end of file diff --git a/hosts/nixos/porthos/secrets/aria/rpc-token.age b/hosts/nixos/porthos/secrets/aria/rpc-token.age new file mode 100644 index 0000000..e6a42c5 --- /dev/null +++ b/hosts/nixos/porthos/secrets/aria/rpc-token.age @@ -0,0 +1,7 @@ +age-encryption.org/v1 +-> ssh-ed25519 cKojmg fpiyZo1AR5hCfk/KtbgWCTzz+05/VOUnnaHhWgXQRwc +d2w9IX/kq/T6OwQ1zImsCmzIX2yfFD8hQDbs0IW3ZIA +-> ssh-ed25519 jPowng E9R7p9NCubUQrymjnrNfEjSNIIAXrBQLogNkWsOx8xc +MrWEE5LNtOqAjnwA6byfSa1udnbUtqBy4FhdxipuA+g +--- fKgerjgGs+brvNKnrWdpmOadl34LipMT6Msqse2g3E0 +E9flKYRL-Ƿ\EK{7oXGxT)˜6%LOT**8\@G \ No newline at end of file diff --git a/hosts/nixos/porthos/secrets/backup/credentials.age b/hosts/nixos/porthos/secrets/backup/credentials.age new file mode 100644 index 0000000..63f0d32 Binary files /dev/null and b/hosts/nixos/porthos/secrets/backup/credentials.age differ diff --git a/hosts/nixos/porthos/secrets/backup/password.age b/hosts/nixos/porthos/secrets/backup/password.age new file mode 100644 index 0000000..db3c2fa --- /dev/null +++ b/hosts/nixos/porthos/secrets/backup/password.age @@ -0,0 +1,7 @@ +age-encryption.org/v1 +-> ssh-ed25519 cKojmg O3DMSSPQP9/ehXmzs0xcCGllu7VSzhd6b4Pii8t2vWQ +Ys1nMv2384elWWGW9C8HabvwUeWu52VsQpxx9L/4/dM +-> ssh-ed25519 jPowng ft/9SX5fpG7+7gHMubaFtb+50/gfNgmaofOVq5UjRUE +xMwdFjFdkH0Li+PikaFt0WAZbFUu5daHgkfN8aQQumo +--- 7DVINvXIXdE1MRwIkeajonYsy1cp4HugCxfTeub5SXU +<<{V?fk/I"/5K"(i \ 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..6b68503 Binary files /dev/null and b/hosts/nixos/porthos/secrets/drone/gitea.age differ diff --git a/hosts/nixos/porthos/secrets/drone/secret.age b/hosts/nixos/porthos/secrets/drone/secret.age new file mode 100644 index 0000000..d6e7330 --- /dev/null +++ b/hosts/nixos/porthos/secrets/drone/secret.age @@ -0,0 +1,7 @@ +age-encryption.org/v1 +-> ssh-ed25519 cKojmg 0J8FMcVRf78LYG+dTOFzu3luXwhOjdOg0sx4Jxdccj4 +tdrCcfcYbTZYhL18RG3goiqtyhu3NTn+fJhdIAnU5uA +-> ssh-ed25519 jPowng qlF8nkSEg5fZgai0VP5eTSlZOHyj5IcalTf+QNWITVo +O5aiZX0AJD76ixsu6i9xnnFBQANdsu3h6XzdTQ6KtKU +--- ByMQt9bnbzd8YO0Y93FIYF/lmdbYcOydkYdKxpRQujM ++ܢ6JNmq[ Eb1p)vDPL9̀z!߇'Tad5U: [dύRMpzj \ 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..737777d Binary files /dev/null and b/hosts/nixos/porthos/secrets/drone/ssh/private-key.age differ diff --git a/hosts/nixos/porthos/secrets/forgejo/mail-password.age b/hosts/nixos/porthos/secrets/forgejo/mail-password.age new file mode 100644 index 0000000..67ef695 --- /dev/null +++ b/hosts/nixos/porthos/secrets/forgejo/mail-password.age @@ -0,0 +1,10 @@ +age-encryption.org/v1 +-> ssh-ed25519 cKojmg Lhgx43wR8PtAMf5v1eJxKlUBSAoOLdOOn/QaQrwF8zA +jfUCpgNzkHCNTWCqtErDaLMmg1Oy+s9zUra1JLCi+J4 +-> ssh-ed25519 jPowng kSeQ/SmMrzd8ByVu3YHWeZyKmqFZvQSBnDunkB8e6wc +WRmnfrV5xcRXA9t0ZXx6YvbRl0sX4PTrw63VVKX4Ei4 +--- a+LLM1gP9g1AbUapbeeKaS4cEcRBmPo3MHU2DSWTAds +,F6⬘ ix̏e| + +Ϝ,{ v!z$P;TKW + qG \ No newline at end of file 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..e2e70ac --- /dev/null +++ b/hosts/nixos/porthos/secrets/gitea/mail-password.age @@ -0,0 +1,7 @@ +age-encryption.org/v1 +-> ssh-ed25519 cKojmg 46BI3ItrXRWMivmd/K8bmkKlrYFSr8cbehAkmwCskig +gTjYquH1hDEZ2zWD5P7gN/ejTCH8JJb8bC/VLZ3koeg +-> ssh-ed25519 jPowng 5MqfJlasDbbqlI0dX98NZzHxmYmnnpveyBxa4z48V0o +r7Yiv4+SZiDncD0Xzp5eFSP4f2yjGBOILKxEO1iT3Os +--- l43+JtT28i1YDhNX3hE3Qb7swskOBc5ghDqiyh3rU2s ++)PnWT,.eNW YƱkF4#=)6mȵJ# \ 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..1d9c5ba Binary files /dev/null and b/hosts/nixos/porthos/secrets/lohr/secret.age differ 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..477a4d1 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..94ddf8c --- /dev/null +++ b/hosts/nixos/porthos/secrets/matrix/mail.age @@ -0,0 +1,9 @@ +age-encryption.org/v1 +-> ssh-ed25519 cKojmg u+5VWUy7eFq4boAIOhuKXZYD4mhczaUAcjz4+coVggA +QlBHHgz7uY3TVgex59yZA0XgsIeHi2WN2S+UleC7bMg +-> ssh-ed25519 jPowng IyeI6WUjF8wxe92xD3xY++4ZqXtY8divB39eLWfAtm8 +eGj8w5X2ydS1LJvNSmo56xzRVoUB0iAKKs2NHX968Yc +--- hsYH9lUl3wIErJmBKzlWV+gIR5v6vgPIcNDgd0hiRGc +@lQsȄףD}^{X)nYJhXhg8wӨǂwy(a.0>|PSlO|E鰀BW_)|x4\_F +Zo0=dtsj[0O+R8id8j +g$x òb흭Xg^G$UB*鲡)[tHav7jD.z+[~ 9z`s,_!^Yʯ2HSŏ*@jZ^v~غ@ \ 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..2c8852d --- /dev/null +++ b/hosts/nixos/porthos/secrets/matrix/secret.age @@ -0,0 +1,7 @@ +age-encryption.org/v1 +-> ssh-ed25519 cKojmg 9ArG1AL2o4VPSqLvBo5namaavBJFjRR4hdbc8OKm+hw +m0wEv9GIgbNoHQL8YjGydwn5G7V7ThLDSl9xiTb78rk +-> ssh-ed25519 jPowng +oQOH/HuOBzB0Cpzm1v5NNotwz568nE58n1AnH5dQHI +Ds39zD2SDw0/R/ovQCvFTVPvMl61unLA9xCoIxdzDVY +--- mbuFfQ2A//d0SsmZnTtRNfAo55dxErscu/SKkUCdCjk +m hKWȕ1QqbN:J Ç!ޣҲaDAY|y;bx_ƒ*zF+)_Q"A5ʡ\RZD I4vieU| \ No newline at end of file diff --git a/hosts/nixos/porthos/secrets/mealie/mail.age b/hosts/nixos/porthos/secrets/mealie/mail.age new file mode 100644 index 0000000..b7348ae Binary files /dev/null and b/hosts/nixos/porthos/secrets/mealie/mail.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..00d89a4 --- /dev/null +++ b/hosts/nixos/porthos/secrets/miniflux/credentials.age @@ -0,0 +1,7 @@ +age-encryption.org/v1 +-> ssh-ed25519 cKojmg GiIQnQGT4QjDiK5ZmIdVAeql0TRqdWnfJn+iJEoyIUw +MVymGa6nxOvC9y7r4TTVzeYfncK02NepBuO6MNy/iW0 +-> ssh-ed25519 jPowng WwS0002uSHdTXce1C2z7SJ2dKoiZhTL5FegemJXluFc +kNbTLdVhDdne6BYZP+xokY9qQqW/P64XqNF7eWst2c4 +--- E+WIi5vSkhXvAZMHO7K5SdgSc9xk5SIwH089OTGHgnM +P~ t%5{;בS@_n'cK"%Pv`w|(- ssh-ed25519 cKojmg l5lOlGnbvQ4D2kaSj1dd8Xr+btlNbTkT0SxSz02Vr1E +Cjy73yKL1N8LnjRXXLpxX+wIOFCa8wrG44VjXUND1lI +-> ssh-ed25519 jPowng nYHfkP9dRkxu4Fqh8MgrbdZAc8gk+VGDyxIV6RsSeEM +rKKi1NDoKMMzQ+kUs5ZX4zMqRBI0QwGY7q6K/L9+dLI +--- Umv3UCtXlApug7uuqmwbQN38i8Lx9/b0uhLgbc3OdZM +BLs?sӓs2y +R0!<f9txB7dڊ^ɇLJ&W ssh-ed25519 cKojmg uz/Zf2uv+q7f3CVEoDuThHdqKE12lgHMDaORjZR/R2M +HqsqZouRxocuHOic08c2oURw2I95BM9CzgEv7FPsWwM +-> ssh-ed25519 jPowng N0HG0fB19MUa2fOIdpKxOZOWjdUgEmKOjIP4hkx1JFo +MAgTWh9d5d75iuzfA2eQ5P8ltSXZJ42dAT82YLGy0hs +--- g9KaXbUtmB05NWkGDSyWxEzo9woQaHHb+TtpvXfcpM4 +cX ȧ5 b2-"RSI&Qt[ QZFk.PO ?LSt-Kj \ No newline at end of file diff --git a/hosts/nixos/porthos/secrets/nextcloud/password.age b/hosts/nixos/porthos/secrets/nextcloud/password.age new file mode 100644 index 0000000..9039eea --- /dev/null +++ b/hosts/nixos/porthos/secrets/nextcloud/password.age @@ -0,0 +1,7 @@ +age-encryption.org/v1 +-> ssh-ed25519 cKojmg aQE1Q9JJqHDGJNkolQF1imxVDtX+d5XVQYB281Ql+h0 +95Uf6vTCSDHLKxZ/6A+C2CQLeO+k/Ybw3hXwAxyu7pM +-> ssh-ed25519 jPowng 4LTu2jV0Y4DuY0NotLUsnC/wIkS545Gy80TbyzzIIys +tMooIb5vJZiAnNJmgPXB2zIKdUGiX5Cinv6uA+WOkoY +--- ZFX+fQXePWAVh4Hsf7yR+IQAnj5swJZPu9B/KDhyBes +n>g[-dӲd{*m%><}9ZԚy>VYΑF \ No newline at end of file diff --git a/hosts/nixos/porthos/secrets/nix-cache/cache-key.age b/hosts/nixos/porthos/secrets/nix-cache/cache-key.age new file mode 100644 index 0000000..17732ed --- /dev/null +++ b/hosts/nixos/porthos/secrets/nix-cache/cache-key.age @@ -0,0 +1,7 @@ +age-encryption.org/v1 +-> ssh-ed25519 cKojmg 8hV+BAKLsq8H/cxsklzuK2Q0/CqwjW6q8xMMJ0EaMDI +LUjA2/qZAAeYjW6TtvmifBUo/WGigKwed6p7RnGjRyA +-> ssh-ed25519 jPowng wrJBGPfY2a8HLoqO12/YIzCB40tZmbAlFTVrDrxrCnE +9ARASO4ZPEyQoBIrs/u8FovkzugQ4fIrZObUVBZ+UAs +--- WKMVJ4dhRAUelxXc7bJ6+UNOSrHxI4LhPo3i0RaHk7A +˯9Q5 Ji)TS8+3 |&"qFF+vɂWj˖MOV'\vK$52ھ,l,̹OgڬL78!QR$ićͰ8>BФbфfDc/qWD'g9 \ No newline at end of file diff --git a/hosts/nixos/porthos/secrets/paperless/password.age b/hosts/nixos/porthos/secrets/paperless/password.age new file mode 100644 index 0000000..8d545fd --- /dev/null +++ b/hosts/nixos/porthos/secrets/paperless/password.age @@ -0,0 +1,8 @@ +age-encryption.org/v1 +-> ssh-ed25519 cKojmg 1hbRAuAGrTy6nmkAq+UWua8weywphZsTIGF68YQEOlQ +92Q7uIKv1EiO73wMh53jrTuEkzP6ziBmX9SWXCl4d3w +-> ssh-ed25519 jPowng aPb9v/S/mLW95Qom+swvasqY878RxpxxOkMJA2wb6nY +qu/dzcqciqKzNc28HqFMHA1XnrJy+/wWgbfM1+BrlkE +--- 8PXOozvZzNZQD2OT4a+0XuIQauzUGSvovdfDugmp+bc +x>禩_C9dT5KzЄqcZɾpใv +) \ 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..70cb898 --- /dev/null +++ b/hosts/nixos/porthos/secrets/paperless/secret-key.age @@ -0,0 +1,7 @@ +age-encryption.org/v1 +-> ssh-ed25519 cKojmg r3ZUTfSNcHc1TS2fVtk99Y2xJMMunkwkcR0dQIdiCi4 +LICSnzAaooGy6x4wt0vNM6YtQ4S17QohZNt7lfVrD6Q +-> ssh-ed25519 jPowng KLU68ws4lemr0wWHxm8H8pf1SQAoUZTN4QSPzk2PyHk +6pjH1pI956oaf9ZIHPPq8p3g/mZC5GxWhWkT54Wohf0 +--- cAQbniTwwtTftfXU/dGtA69yF/hh8iB97vHxvkIZMMo +c#=^~?5-wNT̡+!z " Z"2M!p5VjΡѡLyŹ nĊ8zQ+ة9WS0u}YÚ \ No newline at end of file diff --git a/hosts/nixos/porthos/secrets/pdf-edit/login.age b/hosts/nixos/porthos/secrets/pdf-edit/login.age new file mode 100644 index 0000000..7f13f88 --- /dev/null +++ b/hosts/nixos/porthos/secrets/pdf-edit/login.age @@ -0,0 +1,8 @@ +age-encryption.org/v1 +-> ssh-ed25519 cKojmg VYlHgHSLpfKb5bn1XA3aCpfX7M23DgbraLxxOfo9PDk +Rj+mDvAsWX3WwpuhTrOubmo17j/aud5+P87df5bosBA +-> ssh-ed25519 jPowng o9ZFaYrITZ6DjWw07Vk/+TkuU187/ytlEK4sw7G32G4 +zmxlpDvDDEgQFqBVARXeX1ABhvfJ4uAHfa6mIxXzjAY +--- k/d9FWW8/OSo8EllwOBV74pZyX918u54jEljGk3ATUc +4+2{hE7!ҭGA`ׁ_@ߗR_6JL4v,6%#^  BOF|7ܽL]jR +B۾as]xS pbo#J1Q=t}5>O{+. M"7ey \ 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..d50dc28 --- /dev/null +++ b/hosts/nixos/porthos/secrets/podgrab/password.age @@ -0,0 +1,7 @@ +age-encryption.org/v1 +-> ssh-ed25519 cKojmg bICZUDqk/C2divEZu2lxUDsrtS1inSbDbS8hxJSJfHc +FsfueyP6WCesAu5EcXIxxtvbb8RX09qNTN9GvuhYuTw +-> ssh-ed25519 jPowng Uujsu6c+QTXqCNi6c+zxk5tf0UQcG+Qm/SZF4dzSKCY +RPVNNNauz73A8kWA0VSQiMWCerUkxPoXG2MUrFly3Bc +--- 8h4hGasOwZxk+i5aQfg6AzdA1G4wROhxz2rmM9u41b8 +{Rh=42 yЙjMWQ%X ]JK]F?QK \ No newline at end of file diff --git a/hosts/nixos/porthos/secrets/pyload/credentials.age b/hosts/nixos/porthos/secrets/pyload/credentials.age new file mode 100644 index 0000000..089f962 --- /dev/null +++ b/hosts/nixos/porthos/secrets/pyload/credentials.age @@ -0,0 +1,7 @@ +age-encryption.org/v1 +-> ssh-ed25519 cKojmg nJbOfp0/wmFOZLzcWjoGB7wEB8e56aO1NntSmn5KomU +/Vio4Z/t7IPJrdzdwUPidVH3wrouSkwRzNHP0T4z3x0 +-> ssh-ed25519 jPowng QXg/xqs7/VfkYQg3X77w4i53q64bL9oYeTxqb9NVhiQ +sMHIXlmrIxtIr+s0X4lBqev/PPd3AKD5P7AP5K4NeJg +--- gzTn+6+aa4Ptic1lsvSt+r3IEBysHrvMMIyONogMDF0 +ˮUE_ ssh-ed25519 cKojmg bu09lB+fjaPP31cUQZP6EqSPuseucgNK7k9vAS08iS0 ++NGL+b2QD/qGo6hqHvosAXzHZtDvfodmPdcgnrKlD1o +-> ssh-ed25519 jPowng QDCdRBGWhtdvvMCiDH52cZHz1/W7aomhTatZ4+9IKwI +Ou3jjV/O55G1CPgGS33l3eWhhYWrVdwVNPSiE14d5rE +--- q0ssmpG50OX1WaNSInc2hbtH3DbTwQGDU74VGEoMh94 +mCƑ'hK./Xu(g$'M{fK !MZoR՝͟;yb \ No newline at end of file diff --git a/hosts/nixos/porthos/secrets/servarr/cross-seed/configuration.json.age b/hosts/nixos/porthos/secrets/servarr/cross-seed/configuration.json.age new file mode 100644 index 0000000..7a836c3 Binary files /dev/null and b/hosts/nixos/porthos/secrets/servarr/cross-seed/configuration.json.age differ 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..efbd945 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..211bec3 --- /dev/null +++ b/hosts/nixos/porthos/secrets/sso/ambroisie/totp-secret.age @@ -0,0 +1,7 @@ +age-encryption.org/v1 +-> ssh-ed25519 cKojmg 5PHTwnAvJEFtFNLx4nYSiw1duzeC3EO8rosB56bByHg +xOHergQfjUwKszPCPVu5etijODjoVgIPvgEAi5mCTPA +-> ssh-ed25519 jPowng IEzgBVxTlMHHUaUa2NF2VfhWzfpmX/A1QfJgGQbf3SM +hCFkHlJ1fnPqwWB+3YTPqHV82dfWzA8NZxnOHW9Z2iE +--- NV09xfFINHdmpYZYLSSGk81S/JcSDbjiu83ppak9xRE +EmU 7E@,l<*X`JܳKVVBiBEOՈgo-'@nFq \ No newline at end of file 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..1c12470 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..d6db371 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..16f90b6 --- /dev/null +++ b/hosts/nixos/porthos/secrets/transmission/credentials.age @@ -0,0 +1,8 @@ +age-encryption.org/v1 +-> ssh-ed25519 cKojmg Froxrdh4H2Bsj4X2xicyBXHPRlbkRJAOztoTfzxItSM +FnsLS2QYm8mJUO+c152FieLCFkALxxwQLnY4PAj8zsU +-> ssh-ed25519 jPowng pKl4p02M+U5JsiOnM2wXL5bkPwsI3IHjlTutlvez3zM +NSuOFsyV8JqtTq97lNzacJnJ3YZgWp53XxU3mjUlcMQ +--- 2TK2ViFblmDheaYdat/GF0ze1wVsla1EPLaeRdMM4Gs +ըENܞm›2u~Jubt[$T^2ji@xҸ*İg[MHX!6ezDW]<` XPޛ +q*o$< \ 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..864e5be --- /dev/null +++ b/hosts/nixos/porthos/secrets/vikunja/mail.age @@ -0,0 +1,9 @@ +age-encryption.org/v1 +-> ssh-ed25519 cKojmg o5UoCsI4rvzJ84AQsumbyEngllUcSQB0lZw9F0zK82A +xRcqV+QOqvpeMZiNCdWVhiRaEBayf3cv65xcrIKTfyI +-> ssh-ed25519 jPowng zFZTStqeaFy+HJGN3EV29+Qtf+oaXTKsZNnhetvlNz4 +5dsAyBzbJ3If1KO9vvrO5UHvFfKq154xTvQbu50TFGc +--- /MjYRy6PXkzAcTMMqt84/+SkKFcuGbdFZ3BXHZ15yzY +/\Q'K0 +5]QzO\ޕL f^<+Cv )"6ڷ?soHx7}v1&" F%%|'#dVU)?p}odS|T;ɋ]fM x>h3{W.1&L)߱=k0SSb|V)TB:.lrIf8!~Je<3?;k$Hk +6`#VOt0 + \ No newline at end of file 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..d7e292e --- /dev/null +++ b/hosts/nixos/porthos/secrets/wireguard/private-key.age @@ -0,0 +1,8 @@ +age-encryption.org/v1 +-> ssh-ed25519 cKojmg KslHl4v8yCsKZn5TduLgpTfpTi1uOInC9N2e8Ow83FI +NzcJJr8kw1ykAdWRZOeWdNhx0BTgE7FwTKcge+yLJ/w +-> ssh-ed25519 jPowng YGWcOai0A9l2HDZyV0GtD8kEbY/xTUssODFBcseWAkA +nJaHXkipFSHdyektoKV5y1jQrjkvnU7pwZwAymiQm7M +--- IgWkDulol1jRa+pcx7DbEy5pvC+2nrRJHsdQVPvPur0 +Bb<Ōb!E?:=srJCKz5{4`&N057v+1 ++(d{ Q \ 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..11817ff 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..89bcb6b --- /dev/null +++ b/hosts/nixos/porthos/secrets/woodpecker/secret.age @@ -0,0 +1,7 @@ +age-encryption.org/v1 +-> ssh-ed25519 cKojmg tAW2hbBSxsael6cdbN+vI4h1/PMNrWYct8cppCAasn0 +cex/wBTviSIXc8clNm5PGltTYa1Q5PwqlX4BGsNHiyU +-> ssh-ed25519 jPowng YxfhtpytvuhIARQAaJ0w94aOZiGNUOBR0pF+Sp80D2k +nMon/VdYUQTs6LFccDGeIKWeNYib1wwtFmEYZkDZxg0 +--- giL477X0+uZ2Ocvbixt5f5kNc1laj5P79oW8P9XsNP0 +d>cE?nbv_'2յ_6Pu:usE8ϓxuڶ̪x̧C[ .6 qJ5GK)N 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..6c2fadd --- /dev/null +++ b/lib/lists.nix @@ -0,0 +1,33 @@ +{ 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); + + # Transform a nullable value into a list of zero/one element. + # + # nullableToList :: + # (nullable a) -> [ a ] + nullableToList = x: if x != null then [ x ] else [ ]; +} 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/boot.nix b/machines/porthos/boot.nix deleted file mode 100644 index 3b56eb9..0000000 --- a/machines/porthos/boot.nix +++ /dev/null @@ -1,23 +0,0 @@ -# Boot configuration -{ ... }: - -{ - boot = { - # 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"; - }; - - initrd = { - availableKernelModules = [ "uhci_hcd" "ahci" "usbhid" ]; - kernelModules = [ "dm-snapshot" ]; - }; - - kernelModules = [ "kvm-intel" ]; - - extraModulePackages = [ ]; - }; -} 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/networking.nix b/machines/porthos/networking.nix deleted file mode 100644 index e593eeb..0000000 --- a/machines/porthos/networking.nix +++ /dev/null @@ -1,35 +0,0 @@ -# Networking configuration -{ ... }: - -{ - networking = { - hostName = "porthos"; # Define your hostname. - domain = "belanyi.fr"; # Define your domain. - - - # 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; - - interfaces = { - bond0.useDHCP = true; - bonding_masters.useDHCP = true; - dummy0.useDHCP = true; - erspan0.useDHCP = true; - eth0.useDHCP = true; - eth1.useDHCP = true; - gre0.useDHCP = true; - gretap0.useDHCP = true; - ifb0.useDHCP = true; - ifb1.useDHCP = true; - ip6tnl0.useDHCP = true; - sit0.useDHCP = true; - teql0.useDHCP = true; - tunl0.useDHCP = true; - }; - }; - - # Which interface is used to connect to the internet - my.networking.externalInterface = "eth0"; -} 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 deleted file mode 100644 index 082a8da..0000000 --- a/modules/default.nix +++ /dev/null @@ -1,15 +0,0 @@ -# Common modules -{ ... }: - -{ - imports = [ - ./documentation.nix - ./ergodox.nix - ./language.nix - ./media.nix - ./networking.nix - ./nix.nix - ./packages.nix - ./users.nix - ]; -} 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/home/aliases/default.nix b/modules/home/aliases/default.nix new file mode 100644 index 0000000..259f148 --- /dev/null +++ b/modules/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/modules/home/atuin/default.nix b/modules/home/atuin/default.nix new file mode 100644 index 0000000..dbd9690 --- /dev/null +++ b/modules/home/atuin/default.nix @@ -0,0 +1,47 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.home.atuin; +in +{ + options.my.home.atuin = with lib; { + enable = my.mkDisableOption "atuin configuration"; + + # I want the full experience by default + package = mkPackageOption pkgs "atuin" { }; + + daemon = { + enable = my.mkDisableOption "atuin daemon"; + }; + }; + + config = lib.mkIf cfg.enable { + programs.atuin = { + enable = true; + inherit (cfg) package; + + daemon = lib.mkIf cfg.daemon.enable { + enable = true; + }; + + flags = [ + # I *despise* this hijacking of the up key, even though I use Ctrl-p + "--disable-up-arrow" + ]; + + settings = { + # Reasonable date format + dialect = "uk"; + # 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; + # I like being able to edit my commands + enter_accept = false; + }; + }; + }; +} diff --git a/home/bat.nix b/modules/home/bat/default.nix similarity index 65% rename from home/bat.nix rename to modules/home/bat/default.nix index 8485dd3..67d80c2 100644 --- a/home/bat.nix +++ b/modules/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/modules/home/bitwarden/default.nix b/modules/home/bitwarden/default.nix new file mode 100644 index 0000000..0c0dfab --- /dev/null +++ b/modules/home/bitwarden/default.nix @@ -0,0 +1,22 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.home.bitwarden; +in +{ + options.my.home.bitwarden = with lib; { + enable = my.mkDisableOption "bitwarden configuration"; + + pinentry = mkPackageOption pkgs "pinentry" { default = [ "pinentry-tty" ]; }; + }; + + config = lib.mkIf cfg.enable { + programs.rbw = { + enable = true; + + settings = { + email = lib.my.mkMailAddress "bruno" "belanyi.fr"; + inherit (cfg) pinentry; + }; + }; + }; +} diff --git a/modules/home/bluetooth/default.nix b/modules/home/bluetooth/default.nix new file mode 100644 index 0000000..2a4f613 --- /dev/null +++ b/modules/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/modules/home/calibre/default.nix b/modules/home/calibre/default.nix new file mode 100644 index 0000000..de7c126 --- /dev/null +++ b/modules/home/calibre/default.nix @@ -0,0 +1,17 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.home.calibre; +in +{ + options.my.home.calibre = with lib; { + enable = mkEnableOption "calibre configuration"; + + package = mkPackageOption pkgs "calibre" { }; + }; + + config = lib.mkIf cfg.enable { + home.packages = with pkgs; [ + cfg.package + ]; + }; +} diff --git a/modules/home/comma/default.nix b/modules/home/comma/default.nix new file mode 100644 index 0000000..60de863 --- /dev/null +++ b/modules/home/comma/default.nix @@ -0,0 +1,15 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.home.comma; +in +{ + options.my.home.comma = with lib; { + enable = my.mkDisableOption "comma configuration"; + }; + + config = lib.mkIf cfg.enable { + home.packages = with pkgs; [ + ambroisie.comma + ]; + }; +} diff --git a/modules/home/default.nix b/modules/home/default.nix new file mode 100644 index 0000000..1c40377 --- /dev/null +++ b/modules/home/default.nix @@ -0,0 +1,56 @@ +{ ... }: +{ + imports = [ + ./aliases + ./atuin + ./bat + ./bitwarden + ./bluetooth + ./calibre + ./comma + ./delta + ./dircolors + ./direnv + ./discord + ./documentation + ./feh + ./firefox + ./flameshot + ./fzf + ./gammastep + ./gdb + ./git + ./gpg + ./gtk + ./htop + ./jq + ./keyboard + ./mail + ./mpv + ./nix + ./nix-index + ./nixpkgs + ./nm-applet + ./packages + ./pager + ./power-alert + ./secrets + ./ssh + ./terminal + ./tmux + ./udiskie + ./vim + ./wget + ./wm + ./x + ./xdg + ./zathura + ./zsh + ]; + + # First sane reproducible version + home.stateVersion = "20.09"; + + # Start services automatically + systemd.user.startServices = "sd-switch"; +} diff --git a/modules/home/delta/default.nix b/modules/home/delta/default.nix new file mode 100644 index 0000000..58ee031 --- /dev/null +++ b/modules/home/delta/default.nix @@ -0,0 +1,68 @@ +{ config, pkgs, lib, ... }: +let + cfg = config.my.home.delta; +in +{ + options.my.home.delta = with lib; { + enable = my.mkDisableOption "delta configuration"; + + package = mkPackageOption pkgs "delta" { }; + + git = { + enable = my.mkDisableOption "git integration"; + }; + }; + + config = lib.mkIf cfg.enable { + assertions = [ + { + # For its configuration + assertion = cfg.enable -> cfg.git.enable; + message = '' + `config.my.home.delta` must enable `config.my.home.delta.git` to be + properly configured. + ''; + } + { + assertion = cfg.enable -> config.programs.git.enable; + message = '' + `config.my.home.delta` relies on `config.programs.git` to be + enabled. + ''; + } + ]; + + home.packages = [ cfg.package ]; + + programs.git = lib.mkIf cfg.git.enable { + delta = { + enable = true; + inherit (cfg) package; + + 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"; + }; + }; + }; + }; + }; +} diff --git a/modules/home/dircolors/default.nix b/modules/home/dircolors/default.nix new file mode 100644 index 0000000..876b413 --- /dev/null +++ b/modules/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/modules/home/direnv/default.nix b/modules/home/direnv/default.nix new file mode 100644 index 0000000..67beb62 --- /dev/null +++ b/modules/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 = with types; nullOr str; + default = null; + example = "pkgs"; + 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 = lib.mkIf (cfg.defaultFlake != null) { + DIRENV_DEFAULT_FLAKE = cfg.defaultFlake; + }; + }; +} diff --git a/modules/home/direnv/lib/android.sh b/modules/home/direnv/lib/android.sh new file mode 100644 index 0000000..9344aea --- /dev/null +++ b/modules/home/direnv/lib/android.sh @@ -0,0 +1,63 @@ +# shellcheck shell=bash + +# shellcheck disable=2155 +use_android() { + if [ -z "$ANDROID_HOME" ]; then + log_error "use_android: 'ANDROID_HOME' is not defined" + return 1 + fi + + _use_android_find_latest() { + local path="$1" + local version + + version="$(semver_search "$path" "" "")" + if [ -z "$version" ]; then + log_error "use_android: did not find any version at '$path'" + return 1 + fi + + printf '%s' "$version" + } + + # Default to the latest version found + local ndk_version="$(_use_android_find_latest "$ANDROID_HOME/ndk" || return 1)" + local build_tools_version="$(_use_android_find_latest "$ANDROID_HOME/build-tools" || return 1)" + + unset -f _use_android_find_latest + + # Allow changing the default version through a command line switch + while true; do + case "$1" in + -b|--build-tools) + build_tools_version="$2" + shift 2 + if ! [ -e "$ANDROID_HOME/build-tools/$build_tools_version" ]; then + log_error "use_android: build-tools version '$build_tools_version' does not exist" + fi + ;; + -n|--ndk) + ndk_version="$2" + shift 2 + if ! [ -e "$ANDROID_HOME/ndk/$ndk_version" ]; then + log_error "use_android: NDK version '$ndk_version' does not exist" + fi + ;; + --) + shift + break + ;; + *) + break + ;; + esac + done + + export ANDROID_NDK_HOME="$ANDROID_HOME/ndk/$ndk_version" + export ANDROID_ROOT="$ANDROID_HOME" + export ANDROID_SDK_ROOT="$ANDROID_HOME" + export ANDROID_NDK_ROOT="$ANDROID_NDK_HOME" + + PATH_add "$ANDROID_NDK_HOME" + PATH_add "$ANDROID_HOME/build-tools/$build_tools_version" +} diff --git a/modules/home/direnv/lib/nix.sh b/modules/home/direnv/lib/nix.sh new file mode 100644 index 0000000..4b6c547 --- /dev/null +++ b/modules/home/direnv/lib/nix.sh @@ -0,0 +1,69 @@ +# 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}" + # Additional args that should be forwarded to `nix` + local args=() + + # Allow changing the default flake through a command line switch + while true; do + case "$1" in + -b|--broken) + args+=(--impure) + export NIXPKGS_ALLOW_BROKEN=1 + shift + ;; + -f|--flake) + DEFAULT_FLAKE="$2" + shift 2 + ;; + -i|--impure) + args+=(--impure) + shift + ;; + -s|--insecure) + args+=(--impure) + export NIXPKGS_ALLOW_INSECURE=1 + shift + ;; + -u|--unfree) + args+=(--impure) + export NIXPKGS_ALLOW_UNFREE=1 + shift + ;; + --) + shift + break + ;; + *) + break + ;; + esac + done + + + # 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 "${args[@]}" "${packages[@]}" --command "$direnv" dump + + # Clean-up after ourselves (assumes the user does not set them before us) + unset NIXPKGS_ALLOW_BROKEN + unset NIXPKGS_ALLOW_INSECURE + unset NIXPKGS_ALLOW_UNFREE +} diff --git a/modules/home/direnv/lib/postgres.sh b/modules/home/direnv/lib/postgres.sh new file mode 100644 index 0000000..46e171d --- /dev/null +++ b/modules/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/modules/home/direnv/lib/python.sh b/modules/home/direnv/lib/python.sh new file mode 100644 index 0000000..b1be8a9 --- /dev/null +++ b/modules/home/direnv/lib/python.sh @@ -0,0 +1,57 @@ +# 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 init` to create one first' + return 1 + fi + + # create venv if it doesn't exist + poetry run -q -- 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 +} + +layout_uv() { + if ! has uv; then + # shellcheck disable=2016 + log_error 'layout_uv: `uv` is not in PATH' + return 1 + fi + + if [[ ! -f pyproject.toml ]]; then + # shellcheck disable=2016 + log_error 'layout_uv: no pyproject.toml found. Use `uv init` to create one first' + return 1 + fi + + local default_venv="$PWD/.venv" + : "${VIRTUAL_ENV:=$default_venv}" + + # Use non-default venv path if required + if [ "$VIRTUAL_ENV" != "$default_venv" ]; then + export UV_PROJECT_ENVIRONMENT="$VIRTUAL_ENV" + fi + + # create venv if it doesn't exist + uv venv -q --allow-existing + + export VIRTUAL_ENV + export UV_ACTIVE=1 + PATH_add "$VIRTUAL_ENV/bin" + watch_file pyproject.toml + watch_file uv.lock + watch_file .python-version +} diff --git a/modules/home/discord/default.nix b/modules/home/discord/default.nix new file mode 100644 index 0000000..bfa5d40 --- /dev/null +++ b/modules/home/discord/default.nix @@ -0,0 +1,25 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.home.discord; + + jsonFormat = pkgs.formats.json { }; +in +{ + options.my.home.discord = with lib; { + enable = mkEnableOption "discord configuration"; + + package = mkPackageOption pkgs "discord" { }; + }; + + config = lib.mkIf cfg.enable { + home.packages = with pkgs; [ + cfg.package + ]; + + 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/modules/home/documentation/default.nix similarity index 72% rename from home/documentation.nix rename to modules/home/documentation/default.nix index e31665f..1a305e0 100644 --- a/home/documentation.nix +++ b/modules/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/modules/home/feh/default.nix b/modules/home/feh/default.nix new file mode 100644 index 0000000..3a952a2 --- /dev/null +++ b/modules/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/modules/home/firefox/default.nix b/modules/home/firefox/default.nix new file mode 100644 index 0000000..6346dc9 --- /dev/null +++ b/modules/home/firefox/default.nix @@ -0,0 +1,82 @@ +{ 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 { + nativeMessagingHosts = ([ ] + ++ lib.optional cfg.tridactyl.enable pkgs.tridactyl-native + # Watch videos using mpv + ++ lib.optional cfg.ff2mpv.enable pkgs.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 = { + packages = 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/modules/home/firefox/tridactyl/default.nix b/modules/home/firefox/tridactyl/default.nix new file mode 100644 index 0000000..26ddfad --- /dev/null +++ b/modules/home/firefox/tridactyl/default.nix @@ -0,0 +1,26 @@ +{ 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.replaceVars ./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/modules/home/firefox/tridactyl/tridactylrc b/modules/home/firefox/tridactyl/tridactylrc new file mode 100644 index 0000000..775719c --- /dev/null +++ b/modules/home/firefox/tridactyl/tridactylrc @@ -0,0 +1,80 @@ +" Shamelessly taken from bovine3dom's example configuration file from the docs + +" Basics {{{ +" Use dark color scheme +colorscheme dark + +" Make tridactyl open Vim in my preferred 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, LWN +bind ;c hint -Jc [class*="expand"],[class*="togg"],[class="comment_folder"],[class="CommentTitle"] + +" 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 a +bindurl www.google.com F hint -Jbc #search a + +" Only hint search results on DuckDuckGo +bindurl ^https://duckduckgo.com f hint -Jc [data-testid="result"] +bindurl ^https://duckduckgo.com F hint -Jbc [data-testid="result"] + +" 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") +" }}} + +" Disabled websites {{{ +blacklistadd netflix.com +blacklistadd primevideo.com +blacklistadd jellyfin.belanyi.fr +" }}} + +" vim: set filetype=vim foldmethod=marker: diff --git a/modules/home/flameshot/default.nix b/modules/home/flameshot/default.nix new file mode 100644 index 0000000..a9a60a8 --- /dev/null +++ b/modules/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/modules/home/fzf/default.nix b/modules/home/fzf/default.nix new file mode 100644 index 0000000..b7e308f --- /dev/null +++ b/modules/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/modules/home/gammastep/default.nix b/modules/home/gammastep/default.nix new file mode 100644 index 0000000..3e20094 --- /dev/null +++ b/modules/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/modules/home/gdb/default.nix b/modules/home/gdb/default.nix new file mode 100644 index 0000000..1ffc6bd --- /dev/null +++ b/modules/home/gdb/default.nix @@ -0,0 +1,40 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.home.gdb; +in +{ + options.my.home.gdb = with lib; { + enable = my.mkDisableOption "gdb configuration"; + + package = mkPackageOption pkgs "gdb" { }; + + rr = { + enable = my.mkDisableOption "rr configuration"; + + package = mkPackageOption pkgs "rr" { }; + }; + }; + + config = lib.mkIf cfg.enable (lib.mkMerge [ + { + home.packages = with pkgs; [ + cfg.package + ]; + + xdg = { + configFile."gdb/gdbinit".source = ./gdbinit; + stateFile."gdb/.keep".text = ""; + }; + + home.sessionVariables = { + GDBHISTFILE = "${config.xdg.stateHome}/gdb/gdb_history"; + }; + } + + (lib.mkIf cfg.rr.enable { + home.packages = [ + cfg.rr.package + ]; + }) + ]); +} diff --git a/modules/home/gdb/gdbinit b/modules/home/gdb/gdbinit new file mode 100644 index 0000000..86e8c3c --- /dev/null +++ b/modules/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/modules/home/git/default.ignore similarity index 72% rename from home/git/default.ignore rename to modules/home/git/default.ignore index a169a28..d80996e 100644 --- a/home/git/default.ignore +++ b/modules/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/modules/home/git/default.nix similarity index 61% rename from home/git/default.nix rename to modules/home/git/default.nix index da5efae..ca59a5f 100644 --- a/home/git/default.nix +++ b/modules/home/git/default.nix @@ -1,30 +1,43 @@ { 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"; + + # I want the full experience by default + package = mkPackageOption pkgs "git" { default = [ "gitFull" ]; }; }; + 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; + inherit (cfg) package; 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; @@ -62,13 +75,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 +95,9 @@ in defaultBranch = "main"; }; - pager = - let - diff-highlight = "${pkgs.gitAndTools.gitFull}/share/git/contrib/diff-highlight/diff-highlight"; - in - { - diff = "${diff-highlight} | less"; - log = "${diff-highlight} | less"; - show = "${diff-highlight} | less"; - }; + merge = { + conflictStyle = "zdiff3"; + }; pull = { # Avoid useless merge commits @@ -114,11 +114,50 @@ in autoSquash = true; autoStash = true; }; + + rerere = { + enabled = 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/"; } + includes = lib.mkAfter [ + # Multiple identities + { + 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"; + }; + }; + } + # Local configuration, not-versioned + { + path = "config.local"; + } ]; ignores = diff --git a/modules/home/gpg/default.nix b/modules/home/gpg/default.nix new file mode 100644 index 0000000..2a00baf --- /dev/null +++ b/modules/home/gpg/default.nix @@ -0,0 +1,31 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.home.gpg; +in +{ + options.my.home.gpg = with lib; { + enable = my.mkDisableOption "gpg configuration"; + + pinentry = mkPackageOption pkgs "pinentry" { default = [ "pinentry-tty" ]; }; + }; + + config = lib.mkIf cfg.enable { + programs.gpg = { + enable = true; + }; + + services.gpg-agent = { + enable = true; + enableSshSupport = true; # One agent to rule them all + pinentry.package = cfg.pinentry; + extraConfig = '' + allow-loopback-pinentry + ''; + }; + + home.shellAliases = { + # Sometime `gpg-agent` errors out... + reset-agent = "gpg-connect-agent updatestartuptty /bye"; + }; + }; +} diff --git a/modules/home/gtk/default.nix b/modules/home/gtk/default.nix new file mode 100644 index 0000000..f10087d --- /dev/null +++ b/modules/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-themes-extra; + name = "Adwaita"; + }; + + theme = { + package = pkgs.gnome-themes-extra; + name = "Adwaita"; + }; + }; +} diff --git a/home/htop.nix b/modules/home/htop/default.nix similarity index 60% rename from home/htop.nix rename to modules/home/htop/default.nix index c4b33a9..570fa47 100644 --- a/home/htop.nix +++ b/modules/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/modules/home/jq/default.nix similarity index 73% rename from home/jq.nix rename to modules/home/jq/default.nix index d8bd59b..53e5986 100644 --- a/home/jq.nix +++ b/modules/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 { @@ -17,6 +17,7 @@ in strings = "0;32"; arrays = "1;39"; objects = "1;39"; + objectKeys = "1;34"; }; }; } diff --git a/modules/home/keyboard/default.nix b/modules/home/keyboard/default.nix new file mode 100644 index 0000000..2216a08 --- /dev/null +++ b/modules/home/keyboard/default.nix @@ -0,0 +1,16 @@ +{ config, lib, ... }: +let + cfg = config.my.home.keyboard; +in +{ + options.my.home.keyboard = with lib; { + enable = my.mkDisableOption "keyboard configuration"; + }; + + config = lib.mkIf cfg.enable { + home.keyboard = { + layout = "fr"; + variant = "us"; + }; + }; +} diff --git a/modules/home/mail/accounts/default.nix b/modules/home/mail/accounts/default.nix new file mode 100644 index 0000000..5216ad5 --- /dev/null +++ b/modules/home/mail/accounts/default.nix @@ -0,0 +1,79 @@ +{ 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 + }; + + msmtp = { + enable = cfg.msmtp.enable; + }; + }; + + migaduConfig = { + flavor = "migadu.com"; + }; + + 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 configuration + (mkConfig { + domain = "belanyi.fr"; + address = "bruno"; + passName = "Migadu"; + aliases = [ "admin" "postmaster" ]; + primary = true; # This is my primary email + }) + migaduConfig + ]; + + gmail = lib.mkMerge [ + # Common configuration + (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/modules/home/mail/default.nix b/modules/home/mail/default.nix new file mode 100644 index 0000000..14f086a --- /dev/null +++ b/modules/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/modules/home/mail/himalaya/default.nix b/modules/home/mail/himalaya/default.nix new file mode 100644 index 0000000..849a415 --- /dev/null +++ b/modules/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/modules/home/mail/msmtp/default.nix b/modules/home/mail/msmtp/default.nix new file mode 100644 index 0000000..c469982 --- /dev/null +++ b/modules/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/modules/home/mpv/default.nix b/modules/home/mpv/default.nix new file mode 100644 index 0000000..8af394c --- /dev/null +++ b/modules/home/mpv/default.nix @@ -0,0 +1,21 @@ +{ 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 + pkgs.mpvScripts.mpv-cheatsheet # Show some simple mappings on '?' + pkgs.mpvScripts.uosc # Nicer UI + ]; + }; + }; +} diff --git a/modules/home/nix-index/default.nix b/modules/home/nix-index/default.nix new file mode 100644 index 0000000..383621e --- /dev/null +++ b/modules/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/modules/home/nix/default.nix b/modules/home/nix/default.nix new file mode 100644 index 0000000..c67cc6a --- /dev/null +++ b/modules/home/nix/default.nix @@ -0,0 +1,122 @@ +# 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.inputs.overrideNixpkgs { + # ... And with `nix run nixpkgs#` + nixpkgs = inputs.nixpkgs; + }) + ]; +in +{ + options.my.home.nix = with lib; { + enable = my.mkDisableOption "nix configuration"; + + gc = { + enable = my.mkDisableOption "nix GC configuration"; + }; + + cache = { + selfHosted = my.mkDisableOption "self-hosted cache"; + }; + + inputs = { + link = 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.inputs.addToNixPath -> cfg.inputs.link; + 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.gc.enable { + nix.gc = { + automatic = true; + + # Every week, with some wiggle room + frequency = "weekly"; + randomizedDelaySec = "10min"; + + # Use a persistent timer for e.g: laptops + persistent = true; + + # Delete old profiles automatically after 15 days + options = "--delete-older-than 15d"; + }; + }) + + (lib.mkIf cfg.cache.selfHosted { + nix = { + settings = { + extra-substituters = [ + "https://cache.belanyi.fr/" + ]; + + extra-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 { + xdg.configFile = + 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 = [ "${config.xdg.configHome}/nix/inputs" ]; + }) + ]); +} diff --git a/modules/home/nixpkgs/default.nix b/modules/home/nixpkgs/default.nix new file mode 100644 index 0000000..720fc9b --- /dev/null +++ b/modules/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/modules/home/nm-applet/default.nix b/modules/home/nm-applet/default.nix new file mode 100644 index 0000000..b8637f7 --- /dev/null +++ b/modules/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/modules/home/packages/default.nix b/modules/home/packages/default.nix new file mode 100644 index 0000000..43f7111 --- /dev/null +++ b/modules/home/packages/default.nix @@ -0,0 +1,37 @@ +{ config, lib, pkgs, osConfig, ... }: +let + cfg = config.my.home.packages; + useGlobalPkgs = osConfig.home-manager.useGlobalPkgs or false; +in +{ + options.my.home.packages = with lib; { + enable = my.mkDisableOption "user packages"; + + allowAliases = mkEnableOption "allow package aliases"; + + allowUnfree = my.mkDisableOption "allow unfree packages"; + + additionalPackages = mkOption { + type = with types; listOf package; + default = [ ]; + example = literalExample '' + with pkgs; [ + quasselClient + ] + ''; + }; + }; + + config = lib.mkIf cfg.enable { + home.packages = with pkgs; ([ + fd + file + ripgrep + tree + ] ++ cfg.additionalPackages); + + nixpkgs.config = lib.mkIf (!useGlobalPkgs) { + inherit (cfg) allowAliases allowUnfree; + }; + }; +} diff --git a/modules/home/pager/default.nix b/modules/home/pager/default.nix new file mode 100644 index 0000000..e84dcb7 --- /dev/null +++ b/modules/home/pager/default.nix @@ -0,0 +1,26 @@ +{ 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.stateHome}/less/history"; + }; + + xdg.configFile."lesskey".text = '' + # Quit without clearing the screen on `Q` + Q toggle-option -!^Predraw-on-quit\nq + ''; + }; +} diff --git a/modules/home/power-alert/default.nix b/modules/home/power-alert/default.nix new file mode 100644 index 0000000..8dbb5e6 --- /dev/null +++ b/modules/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/modules/home/secrets/default.nix b/modules/home/secrets/default.nix new file mode 100644 index 0000000..7c0c0a1 --- /dev/null +++ b/modules/home/secrets/default.nix @@ -0,0 +1,25 @@ +{ config, inputs, lib, options, ... }: + +{ + imports = [ + inputs.agenix.homeManagerModules.age + ]; + + 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/modules/home/secrets/github/token.age b/modules/home/secrets/github/token.age new file mode 100644 index 0000000..3e8bb5a --- /dev/null +++ b/modules/home/secrets/github/token.age @@ -0,0 +1,6 @@ +age-encryption.org/v1 +-> ssh-ed25519 jPowng uMp72Kbzwmk0Ud45ScLkMea8Qx+HYgXNad99h+E9bkM +MsEZBv0D9OHHp0xDS5Zh641/msxxh0TztQJZcuJhooY +--- AiF3nfeRnD9nYpzl9LFVhYodfEma6d6jcxXyT74pCQI + xfK[ @zMK*q}*HB6 +̡Yd.Ƙ+lK$@i80y/{ \ No newline at end of file diff --git a/modules/home/secrets/secrets.nix b/modules/home/secrets/secrets.nix new file mode 100644 index 0000000..27cdb4e --- /dev/null +++ b/modules/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/modules/home/ssh/default.nix b/modules/home/ssh/default.nix new file mode 100644 index 0000000..748b195 --- /dev/null +++ b/modules/home/ssh/default.nix @@ -0,0 +1,70 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.home.ssh; +in +{ + options.my.home.ssh = with lib; { + enable = my.mkDisableOption "ssh configuration"; + + mosh = { + enable = my.mkDisableOption "mosh configuration"; + + package = mkPackageOption pkgs "mosh" { }; + }; + }; + + config = lib.mkIf cfg.enable (lib.mkMerge [ + { + programs.ssh = { + enable = true; + + includes = [ + # Local configuration, not-versioned + "config.local" + ]; + + matchBlocks = { + "github.com" = { + hostname = "github.com"; + identityFile = "~/.ssh/shared_rsa"; + user = "git"; + }; + + "gitlab.com" = { + hostname = "gitlab.com"; + identityFile = "~/.ssh/shared_rsa"; + user = "git"; + }; + + "git.sr.ht" = { + hostname = "git.sr.ht"; + identityFile = "~/.ssh/shared_rsa"; + user = "git"; + }; + + "git.belanyi.fr" = { + hostname = "git.belanyi.fr"; + identityFile = "~/.ssh/shared_rsa"; + user = "git"; + }; + + porthos = { + hostname = "37.187.146.15"; + identityFile = "~/.ssh/shared_rsa"; + user = "ambroisie"; + }; + }; + + extraConfig = '' + AddKeysToAgent yes + ''; + }; + } + + (lib.mkIf cfg.mosh.enable { + home.packages = [ + cfg.mosh.package + ]; + }) + ]); +} diff --git a/modules/home/terminal/alacritty/default.nix b/modules/home/terminal/alacritty/default.nix new file mode 100644 index 0000000..daf3e80 --- /dev/null +++ b/modules/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/modules/home/terminal/default.nix b/modules/home/terminal/default.nix new file mode 100644 index 0000000..20f36b5 --- /dev/null +++ b/modules/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/modules/home/terminal/termite/default.nix b/modules/home/terminal/termite/default.nix new file mode 100644 index 0000000..e8f67a7 --- /dev/null +++ b/modules/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/modules/home/tmux/default.nix b/modules/home/tmux/default.nix new file mode 100644 index 0000000..3ea047a --- /dev/null +++ b/modules/home/tmux/default.nix @@ -0,0 +1,131 @@ +{ 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) + ]; + + mkTerminalFeature = opt: flag: + let + mkFlag = term: ''set -as terminal-features ",${term}:${flag}"''; + enabledTerminals = lib.filterAttrs (_: v: v.${opt}) cfg.terminalFeatures; + terminals = lib.attrNames enabledTerminals; + in + lib.concatMapStringsSep "\n" mkFlag terminals; +in +{ + options.my.home.tmux = with lib; { + enable = my.mkDisableOption "tmux terminal multiplexer"; + + enablePassthrough = mkEnableOption "tmux DCS passthrough sequence"; + + enableResurrect = mkEnableOption "tmux-resurrect plugin"; + + terminalFeatures = mkOption { + type = with types; attrsOf (submodule { + options = { + hyperlinks = my.mkDisableOption "hyperlinks through OSC8"; + + trueColor = my.mkDisableOption "24-bit (RGB) color support"; + }; + }); + + default = { ${config.my.home.terminal.program} = { }; }; + defaultText = literalExpression '' + { ''${config.my.home.terminal.program} = { }; }; + ''; + example = { xterm-256color = { }; }; + description = '' + $TERM values which should be considered to have additional features. + ''; + }; + }; + + 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 = 1000000; # Bigger buffer + mouse = false; # I dislike mouse support + focusEvents = true; # Report focus events + terminal = "tmux-256color"; # I want accurate termcap info + aggressiveResize = true; # Automatic resize when switching client size + + plugins = with pkgs.tmuxPlugins; builtins.filter (attr: attr != { }) [ + # 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' + ''; + } + # Resurrect sessions + (lib.optionalAttrs cfg.enableResurrect { + plugin = resurrect; + extraConfig = '' + set -g @resurrect-dir '${config.xdg.stateHome}/tmux/resurrect' + ''; + }) + ]; + + extraConfig = '' + # Refresh configuration + bind-key -N "Source tmux.conf" R source-file ${config.xdg.configHome}/tmux/tmux.conf \; display-message "Sourced tmux.conf!" + + # Accept sloppy Ctrl key when switching windows, on top of default mapping + bind-key -N "Select the previous window" C-p previous-window + bind-key -N "Select the next window" C-n next-window + + # 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.enablePassthrough '' + # Allow any application to use the tmux DCS for passthrough + set -g allow-passthrough on + '' + } + + # Force OSC8 hyperlinks for each relevant $TERM + ${mkTerminalFeature "hyperlinks" "hyperlinks"} + # Force 24-bit color for each relevant $TERM + ${mkTerminalFeature "trueColor" "RGB"} + ''; + }; +} diff --git a/modules/home/udiskie/default.nix b/modules/home/udiskie/default.nix new file mode 100644 index 0000000..1f2119e --- /dev/null +++ b/modules/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/beancount.vim b/modules/home/vim/after/ftplugin/beancount.vim similarity index 70% rename from home/vim/after/ftplugin/beancount.vim rename to modules/home/vim/after/ftplugin/beancount.vim index c5645c8..a2b142e 100644 --- a/home/vim/after/ftplugin/beancount.vim +++ b/modules/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/cmake.vim b/modules/home/vim/after/ftplugin/cmake.vim similarity index 100% rename from home/vim/after/ftplugin/cmake.vim rename to modules/home/vim/after/ftplugin/cmake.vim diff --git a/home/vim/after/ftplugin/fugitive.vim b/modules/home/vim/after/ftplugin/fugitive.vim similarity index 100% rename from home/vim/after/ftplugin/fugitive.vim rename to modules/home/vim/after/ftplugin/fugitive.vim diff --git a/home/vim/after/ftplugin/gitcommit.vim b/modules/home/vim/after/ftplugin/gitcommit.vim similarity index 60% rename from home/vim/after/ftplugin/gitcommit.vim rename to modules/home/vim/after/ftplugin/gitcommit.vim index f785ff8..29d182c 100644 --- a/home/vim/after/ftplugin/gitcommit.vim +++ b/modules/home/vim/after/ftplugin/gitcommit.vim @@ -4,3 +4,7 @@ call ftplugined#check_undo_ft() " Enable spell checking on commit messages setlocal spell let b:undo_ftplugin.='|setlocal spell<' + +" Change max length of a line to 72 for this buffer +setlocal colorcolumn=72 +let b:undo_ftplugin.='|setlocal colorcolumn<' diff --git a/home/vim/after/ftplugin/gitconfig.vim b/modules/home/vim/after/ftplugin/gitconfig.vim similarity index 100% rename from home/vim/after/ftplugin/gitconfig.vim rename to modules/home/vim/after/ftplugin/gitconfig.vim diff --git a/modules/home/vim/after/ftplugin/gn.vim b/modules/home/vim/after/ftplugin/gn.vim new file mode 100644 index 0000000..0cec9df --- /dev/null +++ b/modules/home/vim/after/ftplugin/gn.vim @@ -0,0 +1,6 @@ +" Create the `b:undo_ftplugin` variable if it doesn't exist +call ftplugined#check_undo_ft() + +" Set comment string, as it seems that no official GN support exists upstream +setlocal commentstring=#\ %s +let b:undo_ftplugin.='|setlocal commentstring<' diff --git a/modules/home/vim/after/ftplugin/haskell.vim b/modules/home/vim/after/ftplugin/haskell.vim new file mode 100644 index 0000000..978f346 --- /dev/null +++ b/modules/home/vim/after/ftplugin/haskell.vim @@ -0,0 +1,10 @@ +" Create the `b:undo_ftplugin` variable if it doesn't exist +call ftplugined#check_undo_ft() + +" Use a small indentation value on Haskell files +setlocal shiftwidth=2 +let b:undo_ftplugin.='|setlocal shiftwidth<' + +" 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/help.vim b/modules/home/vim/after/ftplugin/help.vim similarity index 100% rename from home/vim/after/ftplugin/help.vim rename to modules/home/vim/after/ftplugin/help.vim diff --git a/modules/home/vim/after/ftplugin/json.vim b/modules/home/vim/after/ftplugin/json.vim new file mode 100644 index 0000000..3f7b09d --- /dev/null +++ b/modules/home/vim/after/ftplugin/json.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 JSON files +setlocal shiftwidth=2 +let b:undo_ftplugin.='|setlocal shiftwidth<' diff --git a/home/vim/after/ftplugin/mail.vim b/modules/home/vim/after/ftplugin/mail.vim similarity index 100% rename from home/vim/after/ftplugin/mail.vim rename to modules/home/vim/after/ftplugin/mail.vim diff --git a/home/vim/after/ftplugin/make.vim b/modules/home/vim/after/ftplugin/make.vim similarity index 100% rename from home/vim/after/ftplugin/make.vim rename to modules/home/vim/after/ftplugin/make.vim diff --git a/home/vim/after/ftplugin/pandoc.vim b/modules/home/vim/after/ftplugin/markdown.vim similarity index 68% rename from home/vim/after/ftplugin/pandoc.vim rename to modules/home/vim/after/ftplugin/markdown.vim index d44bc12..5b58b41 100644 --- a/home/vim/after/ftplugin/pandoc.vim +++ b/modules/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/modules/home/vim/after/ftplugin/nix.vim b/modules/home/vim/after/ftplugin/nix.vim new file mode 100644 index 0000000..fb6126e --- /dev/null +++ b/modules/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/modules/home/vim/after/ftplugin/python.vim b/modules/home/vim/after/ftplugin/python.vim new file mode 100644 index 0000000..e7232a8 --- /dev/null +++ b/modules/home/vim/after/ftplugin/python.vim @@ -0,0 +1,6 @@ +" Create the `b:undo_ftplugin` variable if it doesn't exist +call ftplugined#check_undo_ft() + +" 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/modules/home/vim/after/ftplugin/query.vim b/modules/home/vim/after/ftplugin/query.vim new file mode 100644 index 0000000..fd2ac73 --- /dev/null +++ b/modules/home/vim/after/ftplugin/query.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 query files +setlocal shiftwidth=2 +let b:undo_ftplugin.='|setlocal shiftwidth<' diff --git a/modules/home/vim/after/ftplugin/rust.vim b/modules/home/vim/after/ftplugin/rust.vim new file mode 100644 index 0000000..8738a54 --- /dev/null +++ b/modules/home/vim/after/ftplugin/rust.vim @@ -0,0 +1,6 @@ +" Create the `b:undo_ftplugin` variable if it doesn't exist +call ftplugined#check_undo_ft() + +" 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/tex.vim b/modules/home/vim/after/ftplugin/tex.vim similarity index 100% rename from home/vim/after/ftplugin/tex.vim rename to modules/home/vim/after/ftplugin/tex.vim diff --git a/modules/home/vim/after/ftplugin/tiger.vim b/modules/home/vim/after/ftplugin/tiger.vim new file mode 100644 index 0000000..81c2cfc --- /dev/null +++ b/modules/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/yaml.vim b/modules/home/vim/after/ftplugin/yaml.vim similarity index 100% rename from home/vim/after/ftplugin/yaml.vim rename to modules/home/vim/after/ftplugin/yaml.vim diff --git a/modules/home/vim/after/plugin/mappings/unimpaired.lua b/modules/home/vim/after/plugin/mappings/unimpaired.lua new file mode 100644 index 0000000..765b6b1 --- /dev/null +++ b/modules/home/vim/after/plugin/mappings/unimpaired.lua @@ -0,0 +1,122 @@ +local wk = require("which-key") + +local lsp = require("ambroisie.lsp") + +local keys = { + -- Previous + { "[", group = "Previous" }, + -- Edition and navigation mappings + { "[", desc = "Insert blank line above" }, + { "[", desc = "Previous location list file" }, + { "[", desc = "Previous quickfix list file" }, + { "[", desc = "Previous tag in preview window" }, + { "[a", desc = "Previous argument" }, + { "[A", desc = "First argument" }, + { "[b", desc = "Previous buffer" }, + { "[B", desc = "First buffer" }, + { "[e", desc = "Exchange previous line" }, + { "[f", desc = "Previous file in directory" }, + { "[l", desc = "Previous location list entry" }, + { "[L", desc = "First Location list entry" }, + { "[n", desc = "Previous conflict marker/diff hunk" }, + { "[p", desc = "Paste line above" }, + { "[P", desc = "Paste line above" }, + { "[q", desc = "Previous quickfix list entry" }, + { "[Q", desc = "First quickfix list entry" }, + { "[t", desc = "Previous matching tag" }, + { "[T", desc = "First matching tag" }, + { "[z", desc = "Previous fold" }, + -- Encoding + { "[C", desc = "C string encode" }, + { "[u", desc = "URL encode" }, + { "[x", desc = "XML encode" }, + { "[y", desc = "C string encode" }, + + -- Next + { "]", group = "Next" }, + -- Edition and navigation mappings + { "]", desc = "Insert blank line below" }, + { "]", desc = "Next location list file" }, + { "]", desc = "Next quickfix list file" }, + { "]", desc = "Next tag in preview window" }, + { "]a", desc = "Next argument" }, + { "]A", desc = "Last argument" }, + { "]b", desc = "Next buffer" }, + { "]B", desc = "Last buffer" }, + { "]e", desc = "Exchange next line" }, + { "]f", desc = "Next file in directory" }, + { "]l", desc = "Next location list entry" }, + { "]L", desc = "Last Location list entry" }, + { "]n", desc = "Next conflict marker/diff hunk" }, + { "]p", desc = "Paste line below" }, + { "]P", desc = "Paste line below" }, + { "]q", desc = "Next quickfix list entry" }, + { "]Q", desc = "Last quickfix list entry" }, + { "]t", desc = "Next matching tag" }, + { "]T", desc = "Last matching tag" }, + { "]z", desc = "Next fold" }, + -- Decoding + { "]C", desc = "C string decode" }, + { "]u", desc = "URL decode" }, + { "]x", desc = "XML decode" }, + { "]y", desc = "C string decode" }, + + -- Enable option + { "[o", group = "Enable option" }, + { "[ob", desc = "Light background" }, + { "[oc", desc = "Cursor line" }, + { "[od", desc = "Diff" }, + { "[of", "FormatEnable", desc = "LSP Formatting" }, + { "[oh", desc = "Search high-lighting" }, + { "[oi", desc = "Case insensitive search" }, + { "[ol", desc = "List mode" }, + { "[on", desc = "Line numbers" }, + { "[or", desc = "Relative line numbers" }, + { "[op", "lwindow", desc = "Location list" }, + { "[oq", "cwindow", desc = "Quickfix list" }, + { "[ou", desc = "Cursor column" }, + { "[ov", desc = "Virtual editing" }, + { "[ow", desc = "Text wrapping" }, + { "[ox", desc = "Cursor line and column" }, + { "[oz", desc = "Spell checking" }, + + -- Disable option + { "]o", group = "Disable option" }, + { "]ob", desc = "Light background" }, + { "]oc", desc = "Cursor line" }, + { "]od", desc = "Diff" }, + { "]of", "FormatDisable", desc = "LSP Formatting" }, + { "]oh", desc = "Search high-lighting" }, + { "]oi", desc = "Case insensitive search" }, + { "]ol", desc = "List mode" }, + { "]on", desc = "Line numbers" }, + { "]op", "lclose", desc = "Location list" }, + { "]oq", "cclose", desc = "Quickfix list" }, + { "]or", desc = "Relative line numbers" }, + { "]ou", desc = "Cursor column" }, + { "]ov", desc = "Virtual editing" }, + { "]ow", desc = "Text wrapping" }, + { "]ox", desc = "Cursor line and column" }, + { "]oz", desc = "Spell checking" }, + + -- Toggle option + { "yo", group = "Toggle option" }, + { "yob", desc = "Light background" }, + { "yoc", desc = "Cursor line" }, + { "yod", desc = "Diff" }, + { "yof", "FormatToggle", desc = "LSP Formatting" }, + { "yoh", desc = "Search high-lighting" }, + { "yoi", desc = "Case insensitive search" }, + { "yol", desc = "List mode" }, + { "yon", desc = "Line numbers" }, + { "yop", "(qf_loc_toggle)", desc = "Location list" }, + { "yoq", "(qf_qf_toggle)", desc = "Quickfix list" }, + { "yor", desc = "Relative line numbers" }, + { "you", desc = "Cursor column" }, + { "yov", desc = "Virtual editing" }, + { "yow", desc = "Text wrapping" }, + { "yox", desc = "Cursor line and column" }, + { "yoz", desc = "Spell checking" }, +} + +wk.add(keys) diff --git a/modules/home/vim/after/queries/diff/highlights.scm b/modules/home/vim/after/queries/diff/highlights.scm new file mode 100644 index 0000000..c998725 --- /dev/null +++ b/modules/home/vim/after/queries/diff/highlights.scm @@ -0,0 +1,5 @@ +; extends + +; I want to the line added/removed markers to be the correct color +"+" @diff.plus +"-" @diff.minus diff --git a/modules/home/vim/after/queries/gitcommit/highlights.scm b/modules/home/vim/after/queries/gitcommit/highlights.scm new file mode 100644 index 0000000..05162c9 --- /dev/null +++ b/modules/home/vim/after/queries/gitcommit/highlights.scm @@ -0,0 +1,6 @@ +; extends + +; Highlight over-extended subject lines (rely on wrapping for message body) +((subject) @comment.error + (#vim-match? @comment.error ".\{50,}") + (#offset! @comment.error 0 50 0 0)) diff --git a/home/vim/autoload/ftplugined.vim b/modules/home/vim/autoload/ftplugined.vim similarity index 100% rename from home/vim/autoload/ftplugined.vim rename to modules/home/vim/autoload/ftplugined.vim diff --git a/modules/home/vim/default.nix b/modules/home/vim/default.nix new file mode 100644 index 0000000..930a853 --- /dev/null +++ b/modules/home/vim/default.nix @@ -0,0 +1,107 @@ +{ config, pkgs, lib, ... }: +let + cfg = config.my.home.vim; + configFiles = + let + toSource = directory: { source = ./. + "/${directory}"; }; + configureDirectory = + name: lib.nameValuePair "nvim/${name}" (toSource name); + linkDirectories = + dirs: builtins.listToAttrs (map configureDirectory dirs); + in + linkDirectories [ + "after" + "autoload" + "ftdetect" + "lua" + "plugin" + ]; +in +{ + 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; + vimdiffAlias = true; + + plugins = with pkgs.vimPlugins; [ + # Theming + gruvbox-nvim # Nice dark theme + lualine-nvim # A lua-based status line + lualine-lsp-progress # Show progress for LSP servers + + # tpope essentials + vim-eunuch # UNIX integrations + vim-fugitive # A 'git' wrapper + vim-git # Sane git syntax files + vim-repeat # Enanche '.' for plugins + vim-rsi # Readline mappings + vim-unimpaired # Some ex command mappings + + # Languages + vim-beancount + + # General enhancements + vim-qf # Better quick-fix list + + # Other wrappers + git-messenger-vim # A simple blame window + + # LSP and linting + nvim-lspconfig # Easy LSP configuration + lsp-format-nvim # Simplified formatting configuration + none-ls-nvim # LSP integration for linters and formatters + nvim-treesitter.withAllGrammars # Better highlighting + nvim-treesitter-textobjects # More textobjects + plenary-nvim # 'null-ls', 'telescope' dependency + + # Completion + luasnip # Snippet manager compatible with LSP + nvim-cmp # Completion engine + cmp-async-path # More responsive path completion + cmp-buffer # Words from open buffers + cmp-nvim-lsp # LSP suggestions + cmp-nvim-lua # NeoVim lua API + 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-surround # Deal with pairs, now in Lua + oil-nvim # Better alternative to NetrW + telescope-fzf-native-nvim # Use 'fzf' fuzzy matching algorithm + 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 + bash-language-server + shfmt + + # Generic + typos-lsp + ]; + }; + + config.xdg.configFile = lib.mkIf cfg.enable configFiles; +} diff --git a/modules/home/vim/ftdetect/automake.lua b/modules/home/vim/ftdetect/automake.lua new file mode 100644 index 0000000..68a30ed --- /dev/null +++ b/modules/home/vim/ftdetect/automake.lua @@ -0,0 +1,6 @@ +-- Use Automake filetype for `local.am` files +vim.filetype.add({ + filename = { + ["local.am"] = "automake", + }, +}) diff --git a/modules/home/vim/ftdetect/direnv.lua b/modules/home/vim/ftdetect/direnv.lua new file mode 100644 index 0000000..fba9748 --- /dev/null +++ b/modules/home/vim/ftdetect/direnv.lua @@ -0,0 +1,6 @@ +-- Use bash filetype for `.envrc` files +vim.filetype.add({ + filename = { + [".envrc"] = "bash", + }, +}) diff --git a/modules/home/vim/ftdetect/glsl.lua b/modules/home/vim/ftdetect/glsl.lua new file mode 100644 index 0000000..2f4f1dd --- /dev/null +++ b/modules/home/vim/ftdetect/glsl.lua @@ -0,0 +1,7 @@ +-- Use GLSL filetype for common shader file extensions +vim.filetype.add({ + extension = { + frag = "glsl", + vert = "glsl", + }, +}) diff --git a/modules/home/vim/ftdetect/kconfig.lua b/modules/home/vim/ftdetect/kconfig.lua new file mode 100644 index 0000000..d51e964 --- /dev/null +++ b/modules/home/vim/ftdetect/kconfig.lua @@ -0,0 +1,6 @@ +-- Mconfig is just Kconfig +vim.filetype.add({ + filename = { + ["Mconfig"] = "kconfig", + }, +}) diff --git a/modules/home/vim/ftdetect/tiger.lua b/modules/home/vim/ftdetect/tiger.lua new file mode 100644 index 0000000..a261103 --- /dev/null +++ b/modules/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/init.vim b/modules/home/vim/init.vim similarity index 61% rename from home/vim/init.vim rename to modules/home/vim/init.vim index 8968204..39ef32e 100644 --- a/home/vim/init.vim +++ b/modules/home/vim/init.vim @@ -1,4 +1,4 @@ -" Basic configuraion {{{ +" Basic configuration {{{ """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" " Use UTF-8 set encoding=utf-8 @@ -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 {{{ @@ -31,10 +38,10 @@ set tabstop=8 " File parameters {{{ """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" -" Disable backups, we have source control for that -set nobackup -" Disable swapfiles too +" Disable swap files set noswapfile +" Enable undo files +set undofile " }}} " UI and UX parameters {{{ @@ -59,19 +66,49 @@ 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 + " 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 +" Setup some overrides for gruvbox +lua << EOF +local gruvbox = require("gruvbox") +local colors = gruvbox.palette + +gruvbox.setup({ + overrides = { + -- Only URLs should be underlined + ["@string.special.path"] = { link = "GruvboxOrange" }, + -- Revert back to the better diff highlighting + DiffAdd = { fg = colors.green, bg = "NONE" }, + DiffChange = { fg = colors.aqua, bg = "NONE" }, + DiffDelete = { fg = colors.red, bg = "NONE" }, + DiffText = { fg = colors.yellow, bg = colors.bg0 }, + -- Directories "pop" better in blue + Directory = { link = "GruvboxBlueBold" }, + }, + italic = { + -- Comments should not be italic, for e.g: box drawing + comments = false, + }, +}) +EOF +" Use my preferred colorscheme +colorscheme gruvbox " }}} " Search parameters {{{ @@ -84,12 +121,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/modules/home/vim/lua/ambroisie/lsp.lua b/modules/home/vim/lua/ambroisie/lsp.lua new file mode 100644 index 0000000..fef0487 --- /dev/null +++ b/modules/home/vim/lua/ambroisie/lsp.lua @@ -0,0 +1,88 @@ +local M = {} + +-- Simplified LSP formatting configuration +local lsp_format = require("lsp-format") + +--- shared LSP configuration callback +--- @param client native client configuration +--- @param bufnr int? buffer number of the attached 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() + vim.print(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, + jump = { + -- Show float on jump if no diagnostic text is otherwise shown + float = not (text or lines), + }, + }) + end + + local function show_buffer_diagnostics() + vim.diagnostic.open_float(nil, { scope = "buffer" }) + end + + local function toggle_inlay_hints() + vim.lsp.inlay_hint.enable(not vim.lsp.inlay_hint.is_enabled()) + end + + local keys = { + buffer = bufnr, + -- LSP navigation + { "K", vim.lsp.buf.hover, desc = "Show symbol information" }, + { "", vim.lsp.buf.signature_help, desc = "Show signature information" }, + { "gd", vim.lsp.buf.definition, desc = "Go to definition" }, + { "gD", vim.lsp.buf.declaration, desc = "Go to declaration" }, + { "gi", vim.lsp.buf.implementation, desc = "Go to implementation" }, + { "gr", vim.lsp.buf.references, desc = "List all references" }, + -- Code + { "c", group = "Code" }, + { "ca", vim.lsp.buf.code_action, desc = "Code actions" }, + { "cd", cycle_diagnostics_display, desc = "Cycle diagnostics display" }, + { "cD", show_buffer_diagnostics, desc = "Show buffer diagnostics" }, + { "ch", toggle_inlay_hints, desc = "Toggle inlay hints" }, + { "cr", vim.lsp.buf.rename, desc = "Rename symbol" }, + { "cs", vim.lsp.buf.signature_help, desc = "Show signature" }, + { "ct", vim.lsp.buf.type_definition, desc = "Go to type definition" }, + -- Workspace + { "cw", group = "Workspace" }, + { "cwa", vim.lsp.buf.add_workspace_folder, desc = "Add folder to workspace" }, + { "cwl", list_workspace_folders, desc = "List folders in workspace" }, + { "cwr", vim.lsp.buf.remove_workspace_folder, desc = "Remove folder from workspace" }, + } + + wk.add(keys) +end + +return M diff --git a/modules/home/vim/lua/ambroisie/utils.lua b/modules/home/vim/lua/ambroisie/utils.lua new file mode 100644 index 0000000..0ee7c83 --- /dev/null +++ b/modules/home/vim/lua/ambroisie/utils.lua @@ -0,0 +1,69 @@ +local M = {} + +--- 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(): 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 specific buffer, or all buffers +--- @param bufnr int? buffer number +--- @return table all active LSP client names +M.list_lsp_clients = function(bufnr) + local clients = vim.lsp.get_clients({ bufnr = bufnr }) + local names = {} + + for _, client in ipairs(clients) do + table.insert(names, client.name) + end + + return names +end + +--- partially apply a function with given arguments +M.partial = function(f, ...) + local a = { ... } + local a_len = select("#", ...) + + return function(...) + local tmp = { ... } + local tmp_len = select("#", ...) + + -- Merge arg lists + for i = 1, tmp_len do + a[a_len + i] = tmp[i] + end + + return f(unpack(a, 1, a_len + tmp_len)) + end +end + +return M diff --git a/modules/home/vim/plugin/abbreviations.lua b/modules/home/vim/plugin/abbreviations.lua new file mode 100644 index 0000000..f6d6ac3 --- /dev/null +++ b/modules/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/modules/home/vim/plugin/numbertoggle.lua b/modules/home/vim/plugin/numbertoggle.lua new file mode 100644 index 0000000..b1e3df2 --- /dev/null +++ b/modules/home/vim/plugin/numbertoggle.lua @@ -0,0 +1,24 @@ +-- 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, + callback = function() + if vim.opt.number:get() then + vim.opt.relativenumber = true + end + end, +}) +vim.api.nvim_create_autocmd({ "BufLeave", "FocusLost", "InsertEnter", "WinLeave" }, { + pattern = "*", + group = numbertoggle, + callback = function() + if vim.opt.number:get() then + vim.opt.relativenumber = false + end + end, +}) diff --git a/modules/home/vim/plugin/settings/completion.lua b/modules/home/vim/plugin/settings/completion.lua new file mode 100644 index 0000000..0ed8c7f --- /dev/null +++ b/modules/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 = "async_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/modules/home/vim/plugin/settings/dressing.lua b/modules/home/vim/plugin/settings/dressing.lua new file mode 100644 index 0000000..3928a59 --- /dev/null +++ b/modules/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/modules/home/vim/plugin/settings/formatting.lua b/modules/home/vim/plugin/settings/formatting.lua new file mode 100644 index 0000000..1bb896b --- /dev/null +++ b/modules/home/vim/plugin/settings/formatting.lua @@ -0,0 +1,3 @@ +local lsp_format = require("lsp-format") + +lsp_format.setup({}) diff --git a/modules/home/vim/plugin/settings/git.lua b/modules/home/vim/plugin/settings/git.lua new file mode 100644 index 0000000..b9b92a6 --- /dev/null +++ b/modules/home/vim/plugin/settings/git.lua @@ -0,0 +1,75 @@ +local gitsigns = require("gitsigns") +local utils = require("ambroisie.utils") +local wk = require("which-key") + +--- Transform `f` into a function which acts on the current visual selection +local function make_visual(f) + return function() + local first = vim.fn.line("v") + local last = vim.fn.line(".") + f({ first, last }) + end +end + +local function nav_hunk(dir) + if vim.wo.diff then + local map = { + prev = "[c", + next = "]c", + } + vim.cmd.normal({ map[dir], bang = true }) + else + gitsigns.nav_hunk(dir) + end +end + +gitsigns.setup({ + current_line_blame_opts = { + -- Show the blame quickly + delay = 100, + }, + -- Work-around for https://github.com/lewis6991/gitsigns.nvim/issues/929 + signs_staged_enable = false, +}) + +local keys = { + -- Navigation + { "[c", utils.partial(nav_hunk, "prev"), desc = "Previous hunk/diff" }, + { "]c", utils.partial(nav_hunk, "next"), desc = "Next hunk/diff" }, + -- Commands + { "g", group = "Git" }, + { "gb", gitsigns.toggle_current_line_blame, desc = "Toggle blame virtual text" }, + { "gd", gitsigns.diffthis, desc = "Diff buffer" }, + { "gD", utils.partial(gitsigns.diffthis, "~"), desc = "Diff buffer against last commit" }, + { "gg", "Git", desc = "Git status" }, + { "gh", gitsigns.toggle_deleted, desc = "Show deleted hunks" }, + { "gL", ":spT:Gllog --follow -- %:p", desc = "Current buffer log" }, + { "gm", "(git-messenger)", desc = "Current line blame" }, + { "gp", gitsigns.preview_hunk, desc = "Preview hunk" }, + { "gr", gitsigns.reset_hunk, desc = "Restore hunk" }, + { "gR", gitsigns.reset_buffer, desc = "Restore buffer" }, + { "gs", gitsigns.stage_hunk, desc = "Stage hunk" }, + { "gS", gitsigns.stage_buffer, desc = "Stage buffer" }, + { "gu", gitsigns.undo_stage_hunk, desc = "Undo stage hunk" }, + { "g[", utils.partial(gitsigns.nav_hunk, "prev"), desc = "Previous hunk" }, + { "g]", utils.partial(gitsigns.nav_hunk, "next"), desc = "Next hunk" }, +} + +local objects = { + mode = "o", + { "ih", gitsigns.select_hunk, desc = "Git hunk" }, +} +-- Visual +local visual = { + mode = { "x" }, + { "ih", gitsigns.select_hunk, desc = "Git hunk" }, + { "g", group = "Git" }, + { "gp", gitsigns.preview_hunk, desc = "Preview selection" }, + { "gr", make_visual(gitsigns.reset_hunk), desc = "Restore selection" }, + { "gs", make_visual(gitsigns.stage_hunk), desc = "Stage selection" }, + { "gu", gitsigns.undo_stage_hunk, desc = "Undo stage selection" }, +} + +wk.add(keys) +wk.add(objects) +wk.add(visual) diff --git a/modules/home/vim/plugin/settings/lspconfig.lua b/modules/home/vim/plugin/settings/lspconfig.lua new file mode 100644 index 0000000..7817d4c --- /dev/null +++ b/modules/home/vim/plugin/settings/lspconfig.lua @@ -0,0 +1,115 @@ +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, + jump = { + -- Show float on diagnostic jumps + float = 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 + +-- Haskell +if utils.is_executable("haskell-language-server-wrapper") then + lspconfig.hls.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 + +-- Python +if utils.is_executable("pyright") then + lspconfig.pyright.setup({ + capabilities = capabilities, + on_attach = lsp.on_attach, + }) +end + +if utils.is_executable("ruff") then + lspconfig.ruff.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 + +-- Shell +if utils.is_executable("bash-language-server") then + lspconfig.bashls.setup({ + filetypes = { "bash", "sh", "zsh" }, + capabilities = capabilities, + on_attach = lsp.on_attach, + settings = { + bashIde = { + shfmt = { + -- Simplify the code + simplifyCode = true, + -- Indent switch cases + caseIndent = true, + }, + }, + }, + }) +end + +-- Starlark +if utils.is_executable("starpls") then + lspconfig.starpls.setup({ + capabilities = capabilities, + on_attach = lsp.on_attach, + }) +end + +-- Generic +if utils.is_executable("harper-ls") then + lspconfig.harper_ls.setup({ + capabilities = capabilities, + on_attach = lsp.on_attach, + }) +end + +if utils.is_executable("typos-lsp") then + lspconfig.typos_lsp.setup({ + capabilities = capabilities, + on_attach = lsp.on_attach, + }) +end diff --git a/modules/home/vim/plugin/settings/lualine.lua b/modules/home/vim/plugin/settings/lualine.lua new file mode 100644 index 0000000..bbe4647 --- /dev/null +++ b/modules/home/vim/plugin/settings/lualine.lua @@ -0,0 +1,78 @@ +local lualine = require("lualine") +local oil = require("oil") +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(0) + + 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 = { + { "branch" }, + { "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", + { + sections = { + lualine_a = { + { "mode" }, + }, + lualine_b = { + { "branch" }, + }, + lualine_c = { + function() + return vim.fn.fnamemodify(oil.get_current_dir(), ":~") + end, + }, + }, + filetypes = { "oil" }, + }, + }, +}) diff --git a/modules/home/vim/plugin/settings/null-ls.lua b/modules/home/vim/plugin/settings/null-ls.lua new file mode 100644 index 0000000..258a209 --- /dev/null +++ b/modules/home/vim/plugin/settings/null-ls.lua @@ -0,0 +1,48 @@ +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"), + }), +}) + +-- Nix +null_ls.register({ + null_ls.builtins.formatting.nixpkgs_fmt.with({ + -- Only used if available + condition = utils.is_executable_condition("nixpkgs-fmt"), + }), +}) + +-- Python +null_ls.register({ + 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"), + }), +}) diff --git a/modules/home/vim/plugin/settings/oil.lua b/modules/home/vim/plugin/settings/oil.lua new file mode 100644 index 0000000..74d5007 --- /dev/null +++ b/modules/home/vim/plugin/settings/oil.lua @@ -0,0 +1,36 @@ +local oil = require("oil") +local wk = require("which-key") + +local detail = false + +oil.setup({ + -- Don't show icons + columns = {}, + view_options = { + -- Show files and directories that start with "." by default + show_hidden = true, + -- But never '..' + is_always_hidden = function(name, bufnr) + return name == ".." + end, + }, + keymaps = { + ["gd"] = { + desc = "Toggle file detail view", + callback = function() + detail = not detail + if detail then + oil.set_columns({ "icon", "permissions", "size", "mtime" }) + else + oil.set_columns({ "icon" }) + end + end, + }, + }, +}) + +local keys = { + { "-", oil.open, desc = "Open parent directory" }, +} + +wk.add(keys) diff --git a/modules/home/vim/plugin/settings/surround.lua b/modules/home/vim/plugin/settings/surround.lua new file mode 100644 index 0000000..3585a12 --- /dev/null +++ b/modules/home/vim/plugin/settings/surround.lua @@ -0,0 +1,3 @@ +require("nvim-surround").setup({ + -- No configuration at the moment +}) diff --git a/modules/home/vim/plugin/settings/telescope.lua b/modules/home/vim/plugin/settings/telescope.lua new file mode 100644 index 0000000..810d51c --- /dev/null +++ b/modules/home/vim/plugin/settings/telescope.lua @@ -0,0 +1,36 @@ +local telescope = require("telescope") +local telescope_builtin = require("telescope.builtin") +local wk = require("which-key") + +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") + +local keys = { + { "f", group = "Fuzzy finder" }, + { "fb", telescope_builtin.buffers, desc = "Open buffers" }, + { "ff", telescope_builtin.git_files, desc = "Git tracked files" }, + { "fF", telescope_builtin.find_files, desc = "Files" }, + { "fg", telescope_builtin.live_grep, desc = "Grep string" }, + { "fG", telescope_builtin.grep_string, desc = "Grep string under cursor" }, +} + +wk.add(keys) diff --git a/modules/home/vim/plugin/settings/tree-sitter.lua b/modules/home/vim/plugin/settings/tree-sitter.lua new file mode 100644 index 0000000..d5fff46 --- /dev/null +++ b/modules/home/vim/plugin/settings/tree-sitter.lua @@ -0,0 +1,54 @@ +local ts_config = require("nvim-treesitter.configs") + +ts_config.setup({ + highlight = { + enable = true, + -- Avoid duplicate highlighting + additional_vim_regex_highlighting = false, + }, + indent = { + enable = true, + }, + textobjects = { + select = { + enable = true, + -- Jump to matching text objects + lookahead = true, + keymaps = { + ["aa"] = { query = "@parameter.outer", desc = "a parameter" }, + ["ia"] = { query = "@parameter.inner", desc = "inner parameter" }, + ["ab"] = { query = "@block.outer", desc = "a block" }, + ["ib"] = { query = "@block.inner", desc = "inner block" }, + ["ac"] = { query = "@class.outer", desc = "a class" }, + ["ic"] = { query = "@class.inner", desc = "inner class" }, + ["af"] = { query = "@function.outer", desc = "a function" }, + ["if"] = { query = "@function.inner", desc = "inner function" }, + ["ak"] = { query = "@comment.outer", desc = "a comment" }, + ["aS"] = { query = "@statement.outer", desc = "a statement" }, + }, + }, + move = { + enable = true, + -- Add to jump list + set_jumps = true, + goto_next_start = { + ["]m"] = { query = "@function.outer", desc = "Next method start" }, + ["]S"] = { query = "@statement.outer", desc = "Next statement start" }, + ["]]"] = { query = "@class.outer", desc = "Next class start" }, + }, + goto_next_end = { + ["]M"] = { query = "@function.outer", desc = "Next method end" }, + ["]["] = { query = "@class.outer", desc = "Next class end" }, + }, + goto_previous_start = { + ["[m"] = { query = "@function.outer", desc = "Previous method start" }, + ["[S"] = { query = "@statement.outer", desc = "Previous statement start" }, + ["[["] = { query = "@class.outer", desc = "Previous class start" }, + }, + goto_previous_end = { + ["[M"] = { query = "@function.outer", desc = "Previous method end" }, + ["[]"] = { query = "@class.outer", desc = "Previous class end" }, + }, + }, + }, +}) diff --git a/modules/home/vim/plugin/settings/which-key.lua b/modules/home/vim/plugin/settings/which-key.lua new file mode 100644 index 0000000..3dc260a --- /dev/null +++ b/modules/home/vim/plugin/settings/which-key.lua @@ -0,0 +1,33 @@ +local wk = require("which-key") +wk.setup({ + icons = { + -- I don't like icons + mappings = false, + breadcrumb = "»", + separator = "➜", + group = "+", + ellipsis = "…", + keys = { + Up = " ", + Down = " ", + Left = " ", + Right = " ", + C = "", + M = "", + D = "", + S = "", + CR = "", + Esc = " ", + NL = "", + BS = "", + Space = "", + Tab = " ", + }, + }, +}) + +local keys = { + { "", vim.cmd.nohlsearch, desc = "Clear search highlight" }, +} + +wk.add(keys) diff --git a/modules/home/vim/plugin/signtoggle.lua b/modules/home/vim/plugin/signtoggle.lua new file mode 100644 index 0000000..3deca34 --- /dev/null +++ b/modules/home/vim/plugin/signtoggle.lua @@ -0,0 +1,21 @@ +local signtoggle = vim.api.nvim_create_augroup("signtoggle", { clear = true }) + +-- Only show sign column for the currently focused buffer, if it has a number column +vim.api.nvim_create_autocmd({ "BufEnter", "FocusGained", "WinEnter" }, { + pattern = "*", + group = signtoggle, + callback = function() + if vim.opt.number:get() then + vim.opt.signcolumn = "yes" + end + end, +}) +vim.api.nvim_create_autocmd({ "BufLeave", "FocusLost", "WinLeave" }, { + pattern = "*", + group = signtoggle, + callback = function() + if vim.opt.number:get() then + vim.opt.signcolumn = "no" + end + end, +}) diff --git a/modules/home/wget/default.nix b/modules/home/wget/default.nix new file mode 100644 index 0000000..1be5397 --- /dev/null +++ b/modules/home/wget/default.nix @@ -0,0 +1,26 @@ +{ config, pkgs, lib, ... }: +let + cfg = config.my.home.wget; +in +{ + options.my.home.wget = with lib; { + enable = my.mkDisableOption "wget configuration"; + + package = mkPackageOption pkgs "wget" { }; + }; + + config = lib.mkIf cfg.enable { + home.packages = [ + cfg.package + ]; + + + home.sessionVariables = lib.mkIf cfg.enable { + WGETRC = "${config.xdg.configHome}/wgetrc"; + }; + + xdg.configFile."wgetrc".text = '' + hsts-file = ${config.xdg.stateHome}/wget-hsts + ''; + }; +} diff --git a/modules/home/wm/cursor/default.nix b/modules/home/wm/cursor/default.nix new file mode 100644 index 0000000..3b58b7b --- /dev/null +++ b/modules/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.vimix-cursors; + name = "Vimix-cursors"; + + x11 = { + inherit (cfg_x) enable; + }; + + gtk = { + inherit (cfg_gtk) enable; + }; + }; + }; +} diff --git a/modules/home/wm/default.nix b/modules/home/wm/default.nix new file mode 100644 index 0000000..ae1e136 --- /dev/null +++ b/modules/home/wm/default.nix @@ -0,0 +1,117 @@ +{ 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" ]; + + vpn = { + enable = my.mkDisableOption "VPN configuration"; + + blockConfigs = mkOption { + type = with types; listOf (attrsOf str); + default = [ + { + active_format = " VPN "; + service = "wg-quick-wg"; + } + { + active_format = " VPN (LAN) "; + service = "wg-quick-lan"; + } + ]; + example = [ + { + active_format = " WORK "; + service = "some-service-name"; + } + ]; + description = "list of block configurations, merged with the defaults"; + }; + }; + }; + + 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/modules/home/wm/dunst/default.nix b/modules/home/wm/dunst/default.nix new file mode 100644 index 0000000..949db32 --- /dev/null +++ b/modules/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/modules/home/wm/i3/default.nix b/modules/home/wm/i3/default.nix new file mode 100644 index 0000000..5f22bbe --- /dev/null +++ b/modules/home/wm/i3/default.nix @@ -0,0 +1,391 @@ +{ 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 = "^firefox$"; instance = "Places"; window_role = "Organizer"; } + { class = "^pavucontrol.*$"; } + { class = "^Arandr$"; } + { class = "^\\.blueman-manager-wrapped$"; } + { class = "^\\.arandr-wrapped$"; } + ]; + }; + + 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} -i"; + }) + ( + # 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 = [ + # NOTE: rely on systemd user services instead... + ]; + + window = { + commands = [ + # Make htop window bigger + { + criteria = { title = "^htop$"; }; + command = "resize set 80 ppt 80 ppt, move position center"; + } + ]; + }; + }; + }; + }; +} diff --git a/modules/home/wm/i3bar/default.nix b/modules/home/wm/i3bar/default.nix new file mode 100644 index 0000000..5ae0e7d --- /dev/null +++ b/modules/home/wm/i3bar/default.nix @@ -0,0 +1,113 @@ +{ 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 != { }) (lib.flatten [ + { + 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 |}"; + } + (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"; + } + (lib.optionals cfg.vpn.enable + ( + let + defaults = { + block = "service_status"; + active_state = "Good"; + inactive_format = ""; + inactive_state = "Idle"; + }; + in + builtins.map (block: defaults // block) cfg.vpn.blockConfigs + ) + ) + { + 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/modules/home/wm/rofi/default.nix b/modules/home/wm/rofi/default.nix new file mode 100644 index 0000000..9707ed7 --- /dev/null +++ b/modules/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/modules/home/wm/screen-lock/default.nix b/modules/home/wm/screen-lock/default.nix new file mode 100644 index 0000000..5e6874e --- /dev/null +++ b/modules/home/wm/screen-lock/default.nix @@ -0,0 +1,56 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.home.wm.screen-lock; + + notificationCmd = + 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" + notificationCmd + ]; + }; + }; + }; +} diff --git a/modules/home/x/default.nix b/modules/home/x/default.nix new file mode 100644 index 0000000..c320e52 --- /dev/null +++ b/modules/home/x/default.nix @@ -0,0 +1,17 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.home.x; +in +{ + 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/xdg.nix b/modules/home/xdg/default.nix similarity index 52% rename from home/xdg.nix rename to modules/home/xdg/default.nix index bdeb326..803167f 100644 --- a/home/xdg.nix +++ b/modules/home/xdg/default.nix @@ -3,15 +3,15 @@ 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 { enable = true; # File types mime.enable = true; - # File associatons + # File associations mimeApps = { enable = true; }; @@ -30,19 +30,30 @@ in }; # A tidy home is a tidy mind dataFile = { + "tig/.keep".text = ""; # `tig` uses `XDG_DATA_HOME` specifically... + }; + stateFile = { "bash/.keep".text = ""; - "tig/.keep".text = ""; + "python/.keep".text = ""; }; }; # I want a tidier home config.home.sessionVariables = with config.xdg; lib.mkIf cfg.enable { + ANDROID_HOME = "${dataHome}/android"; + ANDROID_USER_HOME = "${configHome}/android"; CARGO_HOME = "${dataHome}/cargo"; DOCKER_CONFIG = "${configHome}/docker"; - HISTFILE = "${dataHome}/bash/history"; + GRADLE_USER_HOME = "${dataHome}/gradle"; + HISTFILE = "${stateHome}/bash/history"; INPUTRC = "${configHome}/readline/inputrc"; - LESSHISTFILE = "${dataHome}/less/history"; - LESSKEY = "${configHome}/less/lesskey"; - WGETRC = "${configHome}/wgetrc"; + PSQL_HISTORY = "${stateHome}/psql_history"; + PYTHONPYCACHEPREFIX = "${cacheHome}/python/"; + PYTHONUSERBASE = "${dataHome}/python/"; + PYTHON_HISTORY = "${stateHome}/python/history"; + REDISCLI_HISTFILE = "${stateHome}/redis/rediscli_history"; + REPO_CONFIG_DIR = "${configHome}/repo"; + XCOMPOSECACHE = "${dataHome}/X11/xcompose"; + _JAVA_OPTIONS = "-Djava.util.prefs.userRoot=${configHome}/java"; }; } diff --git a/modules/home/zathura/default.nix b/modules/home/zathura/default.nix new file mode 100644 index 0000000..6162542 --- /dev/null +++ b/modules/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/modules/home/zsh/completion-styles.zsh similarity index 89% rename from home/zsh/completion-styles.zsh rename to modules/home/zsh/completion-styles.zsh index a0181a5..c3d664d 100644 --- a/home/zsh/completion-styles.zsh +++ b/modules/home/zsh/completion-styles.zsh @@ -1,4 +1,7 @@ +# shellcheck shell=bash + # Style the completion a bit +# shellcheck disable=2086,2296 zstyle ':completion:*' list-colors ${(s.:.)LS_COLORS} # Show a prompt on selection zstyle ':completion:*' select-prompt '%SScrolling active: current selection at %p%s' @@ -8,6 +11,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' @@ -19,6 +24,7 @@ zstyle ':completion:*:*:(^rm):*:*files' ignored-patterns '*?.o' '*?.c~' '*?.old' # command for process lists, the local web server details and host completion # on processes completion complete all user processes +# shellcheck disable=2016 zstyle ':completion:*:processes' command 'ps -au$USER' # Completion formatting and messages diff --git a/modules/home/zsh/default.nix b/modules/home/zsh/default.nix new file mode 100644 index 0000000..08b4101 --- /dev/null +++ b/modules/home/zsh/default.nix @@ -0,0 +1,156 @@ +{ config, pkgs, lib, ... }: +let + cfg = config.my.home.zsh; +in +{ + options.my.home.zsh = with lib; { + enable = my.mkDisableOption "zsh configuration"; + + launchTmux = mkEnableOption "auto launch tmux at shell start"; + + notify = { + enable = mkEnableOption "zsh-done notification"; + + exclude = mkOption { + type = with types; listOf str; + default = [ + "delta" + "direnv reload" + "fg" + "git (?!push|pull|fetch)" + "htop" + "less" + "man" + "nvim" + "tail -f" + "tmux" + "vim" + ]; + example = [ "command --long-running-option" ]; + description = '' + List of exclusions which should not be create a notification. Accepts + Perl regexes (implicitly anchored with `^\s*`). + ''; + }; + + ssh = { + enable = mkEnableOption "notify through SSH/non-graphical connections"; + + useOsc777 = lib.my.mkDisableOption "use OSC-777 for notifications"; + }; + }; + }; + + config = lib.mkIf cfg.enable (lib.mkMerge [ + { + home.packages = with pkgs; [ + zsh-completions + ]; + + programs.zsh = { + enable = true; + dotDir = "${config.xdg.configHome}/zsh"; # Don't clutter $HOME + enableCompletion = true; + + history = { + size = 500000; + save = 500000; + extended = true; + expireDuplicatesFirst = true; + ignoreSpace = true; + ignoreDups = true; + share = false; + path = "${config.xdg.stateHome}/zsh/zsh_history"; + }; + + 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"; + + initContent = lib.mkMerge [ + # Make those happen early to avoid doing double the work + (lib.mkBefore (lib.optionalString cfg.launchTmux '' + # Launch tmux unless already inside one + if [ -z "$TMUX" ]; then + exec tmux new-session + fi + '')) + + (lib.mkAfter '' + 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; + }; + } + + (lib.mkIf cfg.notify.enable { + programs.zsh = { + plugins = [ + { + name = "zsh-done"; + file = "share/zsh/site-functions/done.plugin.zsh"; + src = pkgs.ambroisie.zsh-done; + } + ]; + + # `localVariables` values don't get merged correctly due to their type, + # don't use `mkIf` + localVariables = { + DONE_EXCLUDE = + let + joined = lib.concatMapStringsSep "|" (c: "(${c})") cfg.notify.exclude; + in + ''^\s*(${joined})''; + } + # Enable `zsh-done` through SSH, if configured + // lib.optionalAttrs cfg.notify.ssh.enable { + DONE_ALLOW_NONGRAPHICAL = 1; + }; + + # Use OSC-777 to send the notification through SSH + initContent = lib.mkIf cfg.notify.ssh.useOsc777 '' + done_send_notification() { + local exit_status="$1" + local title="$2" + local message="$3" + + ${lib.getExe pkgs.ambroisie.osc777} "$title" "$message" + } + ''; + }; + }) + ]); +} diff --git a/modules/home/zsh/extra-mappings.zsh b/modules/home/zsh/extra-mappings.zsh new file mode 100644 index 0000000..25c230d --- /dev/null +++ b/modules/home/zsh/extra-mappings.zsh @@ -0,0 +1,132 @@ +# shellcheck shell=bash + +# Fix Ctrl+u killing from the cursor instead of the whole 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 +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 +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/modules/home/zsh/options.zsh b/modules/home/zsh/options.zsh new file mode 100644 index 0000000..7bcad03 --- /dev/null +++ b/modules/home/zsh/options.zsh @@ -0,0 +1,20 @@ +# shellcheck shell=bash + +# Show an error when a globbing expansion doesn't find any match +setopt nomatch +# List on ambiguous completion and Insert first match immediately +setopt auto_list menu_complete +# Use pushd when cd-ing around +setopt auto_pushd pushd_minus pushd_silent +# Use single quotes in string without the weird escape tricks +setopt rc_quotes +# Single word commands can resume an existing job +setopt auto_resume +# Show history expansion before running a command +setopt hist_verify +# Append commands to history as they are executed +setopt inc_append_history_time +# Remove useless whitespace from commands +setopt hist_reduce_blanks +# Those options aren't wanted +unsetopt beep extended_glob notify 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/nixos/default.nix b/modules/nixos/default.nix new file mode 100644 index 0000000..3648631 --- /dev/null +++ b/modules/nixos/default.nix @@ -0,0 +1,29 @@ +# Common modules +{ lib, ... }: + +{ + imports = [ + ./hardware + ./home + ./profiles + ./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/nixos/hardware/bluetooth/default.nix b/modules/nixos/hardware/bluetooth/default.nix new file mode 100644 index 0000000..b14ac21 --- /dev/null +++ b/modules/nixos/hardware/bluetooth/default.nix @@ -0,0 +1,38 @@ +{ 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 { + services.pulseaudio = { + extraModules = [ pkgs.pulseaudio-modules-bt ]; + package = pkgs.pulseaudioFull; + }; + }) + + # Support for A2DP audio profile + (lib.mkIf cfg.enableHeadsetIntegration { + hardware.bluetooth.settings = { + General = { + Enable = "Source,Sink,Media,Socket"; + }; + }; + }) + ]); +} diff --git a/modules/nixos/hardware/default.nix b/modules/nixos/hardware/default.nix new file mode 100644 index 0000000..8e125ca --- /dev/null +++ b/modules/nixos/hardware/default.nix @@ -0,0 +1,15 @@ +# Hardware-related modules +{ ... }: + +{ + imports = [ + ./bluetooth + ./ergodox + ./firmware + ./graphics + ./networking + ./sound + ./trackball + ./upower + ]; +} diff --git a/modules/ergodox.nix b/modules/nixos/hardware/ergodox/default.nix similarity index 64% rename from modules/ergodox.nix rename to modules/nixos/hardware/ergodox/default.nix index cbc4957..77f3ecb 100644 --- a/modules/ergodox.nix +++ b/modules/nixos/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/nixos/hardware/firmware/default.nix b/modules/nixos/hardware/firmware/default.nix new file mode 100644 index 0000000..a77135c --- /dev/null +++ b/modules/nixos/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/nixos/hardware/graphics/default.nix b/modules/nixos/hardware/graphics/default.nix new file mode 100644 index 0000000..7d8b359 --- /dev/null +++ b/modules/nixos/hardware/graphics/default.nix @@ -0,0 +1,84 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.hardware.graphics; +in +{ + options.my.hardware.graphics = with lib; { + enable = mkEnableOption "graphics configuration"; + + gpuFlavor = mkOption { + type = with types; nullOr (enum [ "amd" "intel" ]); + default = null; + example = "intel"; + description = "Which kind of GPU to install driver for"; + }; + + amd = { + enableKernelModule = lib.my.mkDisableOption "Kernel driver module"; + + amdvlk = lib.mkEnableOption "Use AMDVLK instead of Mesa RADV driver"; + }; + + intel = { + enableKernelModule = lib.my.mkDisableOption "Kernel driver module"; + }; + }; + + config = lib.mkIf cfg.enable (lib.mkMerge [ + { + hardware.graphics = { + enable = true; + }; + } + + # AMD GPU + (lib.mkIf (cfg.gpuFlavor == "amd") { + hardware.amdgpu = { + initrd.enable = cfg.amd.enableKernelModule; + # Vulkan + amdvlk = lib.mkIf cfg.amd.amdvlk { + enable = true; + support32Bit = { + enable = true; + }; + }; + }; + + hardware.graphics = { + extraPackages = with pkgs; [ + # OpenCL + rocmPackages.clr + rocmPackages.clr.icd + ]; + }; + }) + + # Intel GPU + (lib.mkIf (cfg.gpuFlavor == "intel") { + boot.initrd.kernelModules = lib.mkIf cfg.intel.enableKernelModule [ "i915" ]; + + environment.variables = { + VDPAU_DRIVER = "va_gl"; + }; + + hardware.graphics = { + extraPackages = with pkgs; [ + # Open CL + intel-compute-runtime + + # VA API + intel-media-driver + intel-vaapi-driver + libvdpau-va-gl + ]; + + extraPackages32 = with pkgs.driversi686Linux; [ + # VA API + intel-media-driver + intel-vaapi-driver + libvdpau-va-gl + ]; + }; + }) + ]); +} diff --git a/modules/nixos/hardware/networking/default.nix b/modules/nixos/hardware/networking/default.nix new file mode 100644 index 0000000..f0806fe --- /dev/null +++ b/modules/nixos/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/nixos/hardware/sound/default.nix b/modules/nixos/hardware/sound/default.nix new file mode 100644 index 0000000..cd453de --- /dev/null +++ b/modules/nixos/hardware/sound/default.nix @@ -0,0 +1,60 @@ +{ 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 { + services.pulseaudio.enable = true; + }) + ]); +} diff --git a/modules/nixos/hardware/trackball/default.nix b/modules/nixos/hardware/trackball/default.nix new file mode 100644 index 0000000..a9b24e3 --- /dev/null +++ b/modules/nixos/hardware/trackball/default.nix @@ -0,0 +1,27 @@ +# Hold down the `next page` button to scroll using the ball +{ config, lib, ... }: +let + cfg = config.my.hardware.trackball; +in +{ + options.my.hardware.trackball = with lib; { + enable = mkEnableOption "trackball 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 overridden + inputClassSections = lib.mkAfter [ + # MX Ergo + '' + Identifier "MX Ergo scroll button configuration" + MatchProduct "MX Ergo" + MatchIsPointer "on" + Option "ScrollMethod" "button" + Option "ScrollButton" "9" + '' + ]; + }; + }; +} diff --git a/modules/nixos/hardware/upower/default.nix b/modules/nixos/hardware/upower/default.nix new file mode 100644 index 0000000..95fa282 --- /dev/null +++ b/modules/nixos/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/nixos/home/default.nix b/modules/nixos/home/default.nix new file mode 100644 index 0000000..fe00704 --- /dev/null +++ b/modules/nixos/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 "${inputs.self}/modules/home"; + + # Nix Flakes compatibility + useGlobalPkgs = true; + useUserPackages = true; + + # Forward inputs to home-manager configuration + extraSpecialArgs = { + inherit inputs; + }; + }; + }; +} diff --git a/modules/nixos/profiles/bluetooth/default.nix b/modules/nixos/profiles/bluetooth/default.nix new file mode 100644 index 0000000..292d0d1 --- /dev/null +++ b/modules/nixos/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/modules/nixos/profiles/default.nix b/modules/nixos/profiles/default.nix new file mode 100644 index 0000000..dbd4be3 --- /dev/null +++ b/modules/nixos/profiles/default.nix @@ -0,0 +1,12 @@ +# Configuration that spans across system and home, or are almagations of modules +{ ... }: +{ + imports = [ + ./bluetooth + ./devices + ./gtk + ./laptop + ./wm + ./x + ]; +} diff --git a/modules/nixos/profiles/devices/default.nix b/modules/nixos/profiles/devices/default.nix new file mode 100644 index 0000000..7a84bd2 --- /dev/null +++ b/modules/nixos/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; + + trackball.enable = true; + }; + + # MTP devices auto-mount via file explorers + services.gvfs.enable = true; + }; +} diff --git a/modules/nixos/profiles/gtk/default.nix b/modules/nixos/profiles/gtk/default.nix new file mode 100644 index 0000000..a8d6d9a --- /dev/null +++ b/modules/nixos/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/modules/nixos/profiles/laptop/default.nix b/modules/nixos/profiles/laptop/default.nix new file mode 100644 index 0000000..68c65b8 --- /dev/null +++ b/modules/nixos/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.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/modules/nixos/profiles/wm/default.nix b/modules/nixos/profiles/wm/default.nix new file mode 100644 index 0000000..bca4d70 --- /dev/null +++ b/modules/nixos/profiles/wm/default.nix @@ -0,0 +1,31 @@ +{ 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; + # Ensure i3lock can actually unlock the session + security.pam.services.i3lock.enable = true; + }) + ]; +} diff --git a/modules/nixos/profiles/x/default.nix b/modules/nixos/profiles/x/default.nix new file mode 100644 index 0000000..ea77939 --- /dev/null +++ b/modules/nixos/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/modules/nixos/programs/default.nix b/modules/nixos/programs/default.nix new file mode 100644 index 0000000..73f4e4b --- /dev/null +++ b/modules/nixos/programs/default.nix @@ -0,0 +1,8 @@ +# Program-related modules +{ ... }: + +{ + imports = [ + ./steam + ]; +} diff --git a/modules/nixos/programs/steam/default.nix b/modules/nixos/programs/steam/default.nix new file mode 100644 index 0000000..0c7f9da --- /dev/null +++ b/modules/nixos/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/nixos/secrets/default.nix b/modules/nixos/secrets/default.nix new file mode 100644 index 0000000..c7d6f65 --- /dev/null +++ b/modules/nixos/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/nixos/secrets/secrets.nix b/modules/nixos/secrets/secrets.nix new file mode 100644 index 0000000..0e685d9 --- /dev/null +++ b/modules/nixos/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/nixos/secrets/users/ambroisie/hashed-password.age b/modules/nixos/secrets/users/ambroisie/hashed-password.age new file mode 100644 index 0000000..09a80f4 --- /dev/null +++ b/modules/nixos/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 +]c3x w' ` h=XǑg3]~q.Xna*W:,zvyzI }DO=`w7:Rx5$6:",HM"_MMBJeF \ No newline at end of file diff --git a/modules/nixos/secrets/users/root/hashed-password.age b/modules/nixos/secrets/users/root/hashed-password.age new file mode 100644 index 0000000..14986f1 Binary files /dev/null and b/modules/nixos/secrets/users/root/hashed-password.age differ diff --git a/services/adblock.nix b/modules/nixos/services/adblock/default.nix similarity index 63% rename from services/adblock.nix rename to modules/nixos/services/adblock/default.nix index 23d63c8..45e4d6e 100644 --- a/services/adblock.nix +++ b/modules/nixos/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/modules/nixos/services/aria/default.nix b/modules/nixos/services/aria/default.nix new file mode 100644 index 0000000..acbf0b7 --- /dev/null +++ b/modules/nixos/services/aria/default.nix @@ -0,0 +1,74 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.services.aria; +in +{ + options.my.services.aria = with lib; { + enable = mkEnableOption ""; + + rpcSecretFile = mkOption { + type = types.str; + example = "/run/secrets/aria-secret.txt"; + description = '' + File containing the RPC secret. + ''; + }; + + rpcPort = mkOption { + type = types.port; + default = 6800; + example = 8080; + description = "RPC port"; + }; + + downloadDir = mkOption { + type = types.str; + default = "/data/downloads"; + example = "/var/lib/transmission/download"; + description = "Download directory"; + }; + }; + + config = lib.mkIf cfg.enable { + services.aria2 = { + enable = true; + + inherit (cfg) downloadDir rpcSecretFile; + + rpcListenPort = cfg.rpcPort; + openPorts = false; # I don't want to expose the RPC port + }; + + # Expose DHT ports + networking.firewall = { + # FIXME: check for overlap? + allowedUDPPortRanges = config.services.aria2.listenPortRange; + }; + + # Set-up media group + users.groups.media = { }; + + systemd.services.aria2 = { + serviceConfig = { + Group = lib.mkForce "media"; # Use 'media' group + }; + }; + + my.services.nginx.virtualHosts = { + aria = { + root = "${pkgs.ariang}/share/ariang"; + # For paranoia, don't allow anybody to use the UI unauthenticated + sso = { + enable = true; + }; + }; + aria-rpc = { + port = cfg.rpcPort; + # Proxy websockets for RPC + websocketsLocations = [ "/" ]; + }; + }; + + # NOTE: unfortunately aria2 does not log connection failures for fail2ban + }; +} diff --git a/modules/nixos/services/audiobookshelf/default.nix b/modules/nixos/services/audiobookshelf/default.nix new file mode 100644 index 0000000..04ec8b9 --- /dev/null +++ b/modules/nixos/services/audiobookshelf/default.nix @@ -0,0 +1,53 @@ +# Audiobook and podcast library +{ config, lib, ... }: +let + cfg = config.my.services.audiobookshelf; +in +{ + options.my.services.audiobookshelf = with lib; { + enable = mkEnableOption "Audiobookshelf, a self-hosted podcast manager"; + + port = mkOption { + type = types.port; + default = 8000; + example = 4242; + description = "The port on which Audiobookshelf will listen for incoming HTTP traffic."; + }; + }; + + config = lib.mkIf cfg.enable { + services.audiobookshelf = { + enable = true; + inherit (cfg) port; + + group = "media"; + }; + + # Set-up media group + users.groups.media = { }; + + my.services.nginx.virtualHosts = { + audiobookshelf = { + inherit (cfg) port; + # Proxy websockets for RPC + websocketsLocations = [ "/" ]; + }; + }; + + services.fail2ban.jails = { + audiobookshelf = '' + enabled = true + filter = audiobookshelf + port = http,https + ''; + }; + + environment.etc = { + "fail2ban/filter.d/audiobookshelf.conf".text = '' + [Definition] + failregex = ^.*ERROR: \[Auth\] Failed login attempt for username ".*" from ip + journalmatch = _SYSTEMD_UNIT=audiobookshelf.service + ''; + }; + }; +} diff --git a/services/backup.nix b/modules/nixos/services/backup/default.nix similarity index 80% rename from services/backup.nix rename to modules/nixos/services/backup/default.nix index da45b5e..8aeeae1 100644 --- a/services/backup.nix +++ b/modules/nixos/services/backup/default.nix @@ -89,16 +89,26 @@ in }; config = lib.mkIf cfg.enable { + # Essential files which should always be backed up + my.services.backup.paths = lib.flatten [ + # Should be unique to a given host, used by some software (e.g: ZFS) + "/etc/machine-id" + # Contains the UID/GID map, and other useful state + "/var/lib/nixos" + # SSH host keys (and public keys for convenience) + (builtins.map (key: [ key.path "${key.path}.pub" ]) config.services.openssh.hostKeys) + ]; + 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/nixos/services/blog/default.nix b/modules/nixos/services/blog/default.nix new file mode 100644 index 0000000..e4d2d42 --- /dev/null +++ b/modules/nixos/services/blog/default.nix @@ -0,0 +1,45 @@ +# My blog setup +{ config, lib, ... }: +let + cfg = config.my.services.blog; + domain = config.networking.domain; + + makeHostInfo = subdomain: { + root = "/var/www/${subdomain}"; + }; + + hostsInfo = lib.flip lib.genAttrs 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://${domain}$request_uri"; + }; + }; + + # Those are all subdomains, no problem + my.services.nginx.virtualHosts = hostsInfo; + }; +} diff --git a/services/calibre-web.nix b/modules/nixos/services/calibre-web/default.nix similarity index 58% rename from services/calibre-web.nix rename to modules/nixos/services/calibre-web/default.nix index d4d7ece..b7bf9df 100644 --- a/services/calibre-web.nix +++ b/modules/nixos/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,36 @@ 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 = { + 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/nixos/services/default.nix b/modules/nixos/services/default.nix new file mode 100644 index 0000000..e03eca1 --- /dev/null +++ b/modules/nixos/services/default.nix @@ -0,0 +1,48 @@ +{ ... }: + +{ + imports = [ + ./adblock + ./aria + ./audiobookshelf + ./backup + ./blog + ./calibre-web + ./drone + ./fail2ban + ./flood + ./forgejo + ./gitea + ./grocy + ./homebox + ./jellyfin + ./komga + ./lohr + ./matrix + ./mealie + ./miniflux + ./monitoring + ./navidrome + ./nextcloud + ./nginx + ./nix-cache + ./paperless + ./pdf-edit + ./podgrab + ./postgresql + ./postgresql-backup + ./pyload + ./quassel + ./rss-bridge + ./sabnzbd + ./servarr + ./ssh-server + ./tandoor-recipes + ./thelounge + ./tlp + ./transmission + ./vikunja + ./wireguard + ./woodpecker + ]; +} diff --git a/modules/nixos/services/drone/default.nix b/modules/nixos/services/drone/default.nix new file mode 100644 index 0000000..79c48dd --- /dev/null +++ b/modules/nixos/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/nixos/services/drone/runner-docker/default.nix b/modules/nixos/services/drone/runner-docker/default.nix new file mode 100644 index 0000000..e53c608 --- /dev/null +++ b/modules/nixos/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/nixos/services/drone/runner-exec/default.nix b/modules/nixos/services/drone/runner-exec/default.nix new file mode 100644 index 0000000..a9bb563 --- /dev/null +++ b/modules/nixos/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/nixos/services/drone/server/default.nix b/modules/nixos/services/drone/server/default.nix new file mode 100644 index 0000000..d6148f4 --- /dev/null +++ b/modules/nixos/services/drone/server/default.nix @@ -0,0 +1,54 @@ +{ 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.target" ]; + requires = [ "postgresql.target" ]; + 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"; + ensureDBOwnership = true; + }]; + }; + + my.services.nginx.virtualHosts = { + drone = { + inherit (cfg) port; + }; + }; + }; +} diff --git a/modules/nixos/services/fail2ban/default.nix b/modules/nixos/services/fail2ban/default.nix new file mode 100644 index 0000000..be5f7da --- /dev/null +++ b/modules/nixos/services/fail2ban/default.nix @@ -0,0 +1,37 @@ +# Filter and ban unauthorized access +{ 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/nixos/services/flood/default.nix b/modules/nixos/services/flood/default.nix new file mode 100644 index 0000000..f3fe90b --- /dev/null +++ b/modules/nixos/services/flood/default.nix @@ -0,0 +1,33 @@ +# A nice UI for various torrent clients +{ config, lib, ... }: +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"; + }; + }; + + config = lib.mkIf cfg.enable { + services.flood = { + enable = true; + + inherit (cfg) port; + }; + + my.services.nginx.virtualHosts = { + flood = { + inherit (cfg) port; + }; + }; + + # NOTE: unfortunately flood does not log connection failures for fail2ban + }; +} diff --git a/modules/nixos/services/forgejo/default.nix b/modules/nixos/services/forgejo/default.nix new file mode 100644 index 0000000..511724b --- /dev/null +++ b/modules/nixos/services/forgejo/default.nix @@ -0,0 +1,166 @@ +# A low-resource, full-featured git forge. +{ config, lib, ... }: +let + cfg = config.my.services.forgejo; +in +{ + options.my.services.forgejo = with lib; { + enable = mkEnableOption "Forgejo"; + 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"; + description = "Host for the mail account"; + }; + port = mkOption { + type = types.port; + default = 465; + example = 587; + description = "Port for the mail account"; + }; + user = mkOption { + type = types.str; + example = "forgejo@example.com"; + description = "User for the mail account"; + }; + passwordFile = mkOption { + type = types.str; + example = "/run/secrets/forgejo-mail-password.txt"; + description = "Password for the mail account"; + }; + protocol = mkOption { + type = types.str; + default = "smtps"; + example = "smtp"; + description = "Protocol for connection"; + }; + }; + }; + + config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = cfg.enable -> !config.my.services.gitea.enable; + message = '' + `config.my.services.forgejo` is incompatible with + `config.my.services.gitea`. + ''; + } + ]; + + services.forgejo = + let + inherit (config.networking) domain; + forgejoDomain = "git.${domain}"; + in + { + enable = true; + + user = "git"; + group = "git"; + + lfs.enable = true; + + useWizard = false; + + database = { + type = "postgres"; # Automatic setup + user = "git"; # User needs to be the same as forgejo user + name = "git"; # Name must be the same as user for `ensureDBOwnership` + }; + + # NixOS module uses `forgejo 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; + + secrets = { + mailer = lib.mkIf cfg.mail.enable { + PASSWD = cfg.mail.passwordFile; + }; + }; + + settings = { + DEFAULT = { + APP_NAME = "Ambroisie's forge"; + }; + + server = { + HTTP_PORT = cfg.port; + DOMAIN = forgejoDomain; + ROOT_URL = "https://${forgejoDomain}"; + }; + + mailer = lib.mkIf cfg.mail.enable { + ENABLED = true; + SMTP_ADDR = cfg.mail.host; + SMTP_PORT = cfg.mail.port; + FROM = "Forgejo <${cfg.mail.user}>"; + USER = cfg.mail.user; + PROTOCOL = cfg.mail.protocol; + }; + + service = { + DISABLE_REGISTRATION = true; + }; + + session = { + # only send cookies via HTTPS + COOKIE_SECURE = true; + }; + }; + }; + + users.users.git = { + description = "Forgejo Service"; + home = config.services.forgejo.stateDir; + useDefaultShell = true; + group = "git"; + isSystemUser = true; + }; + users.groups.git = { }; + + my.services.nginx.virtualHosts = { + # Proxy to Forgejo + git = { + inherit (cfg) port; + }; + # Redirect `forgejo.` to actual forge subdomain + forgejo = { + redirect = config.services.forgejo.settings.server.ROOT_URL; + }; + }; + + my.services.backup = { + paths = [ + config.services.forgejo.lfs.contentDir + config.services.forgejo.repositoryRoot + ]; + }; + + services.fail2ban.jails = { + forgejo = '' + enabled = true + filter = forgejo + action = iptables-allports + ''; + }; + + environment.etc = { + "fail2ban/filter.d/forgejo.conf".text = '' + [Definition] + failregex = ^.*(Failed authentication attempt|invalid credentials|Attempted access of unknown user).* from $ + journalmatch = _SYSTEMD_UNIT=forgejo.service + ''; + }; + }; +} diff --git a/modules/nixos/services/gitea/default.nix b/modules/nixos/services/gitea/default.nix new file mode 100644 index 0000000..95bdf42 --- /dev/null +++ b/modules/nixos/services/gitea/default.nix @@ -0,0 +1,150 @@ +# A low-resource, 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"; + description = "Host for the mail account"; + }; + port = mkOption { + type = types.port; + default = 465; + example = 587; + description = "Port 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"; + }; + protocol = mkOption { + type = types.str; + default = "smtps"; + example = "smtp"; + description = "Protocol 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"; + group = "git"; + + lfs.enable = true; + + useWizard = false; + + database = { + type = "postgres"; # Automatic setup + user = "git"; # User needs to be the same as gitea user + name = "git"; # Name must be the same as user for `ensureDBOwnership` + }; + + # 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; + SMTP_ADDR = cfg.mail.host; + SMTP_PORT = cfg.mail.port; + FROM = "Gitea <${cfg.mail.user}>"; + USER = cfg.mail.user; + PROTOCOL = cfg.mail.protocol; + }; + + 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"; + isSystemUser = true; + }; + users.groups.git = { }; + + my.services.nginx.virtualHosts = { + # Proxy to Gitea + git = { + inherit (cfg) port; + }; + # Redirect `gitea.` to actual forge 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/nixos/services/grocy/default.nix b/modules/nixos/services/grocy/default.nix new file mode 100644 index 0000000..9045b03 --- /dev/null +++ b/modules/nixos/services/grocy/default.nix @@ -0,0 +1,42 @@ +# 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; + }; + + # NOTE: unfortunately grocy does not log connection failures for fail2ban + }; +} diff --git a/modules/nixos/services/homebox/default.nix b/modules/nixos/services/homebox/default.nix new file mode 100644 index 0000000..8ed5d77 --- /dev/null +++ b/modules/nixos/services/homebox/default.nix @@ -0,0 +1,48 @@ +# Home inventory made easy +{ config, lib, ... }: +let + cfg = config.my.services.homebox; +in +{ + options.my.services.homebox = with lib; { + enable = mkEnableOption "Homebox home inventory"; + + port = mkOption { + type = types.port; + default = 7745; + example = 8080; + description = "Internal port for webui"; + }; + }; + + config = lib.mkIf cfg.enable { + services.homebox = { + enable = true; + + # Automatic PostgreSQL provisioning + database = { + createLocally = true; + }; + + settings = { + # FIXME: mailer? + HBOX_WEB_PORT = toString cfg.port; + }; + }; + + my.services.nginx.virtualHosts = { + homebox = { + inherit (cfg) port; + websocketsLocations = [ "/api" ]; + }; + }; + + my.services.backup = { + paths = [ + config.services.homebox.settings.HBOX_STORAGE_DATA + ]; + }; + + # NOTE: unfortunately homebox does not log connection failures for fail2ban + }; +} diff --git a/modules/nixos/services/jellyfin/default.nix b/modules/nixos/services/jellyfin/default.nix new file mode 100644 index 0000000..6edeb67 --- /dev/null +++ b/modules/nixos/services/jellyfin/default.nix @@ -0,0 +1,57 @@ +# 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 = { }; + + systemd.services.jellyfin = { + serviceConfig = { + # Loose umask to make Jellyfin metadata more broadly readable + UMask = lib.mkForce "0002"; + }; + }; + + my.services.nginx.virtualHosts = { + jellyfin = { + port = 8096; + websocketsLocations = [ "/socket" ]; + extraConfig = { + locations."/" = { + extraConfig = '' + proxy_buffering off; + ''; + }; + }; + }; + }; + + services.fail2ban.jails = { + jellyfin = '' + enabled = true + filter = jellyfin + port = http,https + ''; + }; + + environment.etc = { + "fail2ban/filter.d/jellyfin.conf".text = '' + [Definition] + failregex = ^.*Authentication request for .* has been denied \(IP: "?"?\)\. + journalmatch = _SYSTEMD_UNIT=jellyfin.service + ''; + }; + }; +} diff --git a/modules/nixos/services/komga/default.nix b/modules/nixos/services/komga/default.nix new file mode 100644 index 0000000..9af3cd1 --- /dev/null +++ b/modules/nixos/services/komga/default.nix @@ -0,0 +1,55 @@ +# A Comics/Manga media server +{ config, lib, ... }: +let + cfg = config.my.services.komga; +in +{ + options.my.services.komga = with lib; { + enable = mkEnableOption "Komga comics server"; + + port = mkOption { + type = types.port; + default = 4584; + example = 8080; + description = "Internal port for webui"; + }; + }; + + config = lib.mkIf cfg.enable { + services.komga = { + enable = true; + + group = "media"; + + settings = { + server.port = cfg.port; + logging.level.org.gotson.komga = "DEBUG"; # Needed for fail2ban + }; + }; + + # Set-up media group + users.groups.media = { }; + + my.services.nginx.virtualHosts = { + komga = { + inherit (cfg) port; + }; + }; + + services.fail2ban.jails = { + komga = '' + enabled = true + filter = komga + port = http,https + ''; + }; + + environment.etc = { + "fail2ban/filter.d/komga.conf".text = '' + [Definition] + failregex = ^.* ip=,.*Bad credentials.*$ + journalmatch = _SYSTEMD_UNIT=komga.service + ''; + }; + }; +} diff --git a/services/lohr.nix b/modules/nixos/services/lohr/default.nix similarity index 61% rename from services/lohr.nix rename to modules/nixos/services/lohr/default.nix index 57f4feb..21ed93b 100644 --- a/services/lohr.nix +++ b/modules/nixos/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,38 +56,55 @@ in Environment = [ "ROCKET_PORT=${toString cfg.port}" "ROCKET_LOG_LEVEL=normal" - "LOHR_HOME=/var/lib/lohr/" + "LOHR_HOME=${lohrHome}" "LOHR_CONFIG=" ]; 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; + my.services.nginx.virtualHosts = { + lohr = { + inherit (cfg) port; + }; + }; - locations."/" = { - proxyPass = "http://127.0.0.1:${toString cfg.port}/"; + # SSH key provisioning + systemd.tmpfiles.settings."10-lohr" = lib.mkIf (cfg.sshKeyFile != null) { + "${lohrHome}/.ssh" = { + d = { + user = "lohr"; + group = "lohr"; + mode = "0700"; + }; + }; + "${lohrHome}/.ssh/id_ed25519" = { + "L+" = { + user = "lohr"; + group = "lohr"; + mode = "0700"; + argument = cfg.sshKeyFile; + }; }; }; }; diff --git a/modules/nixos/services/matrix/bridges.nix b/modules/nixos/services/matrix/bridges.nix new file mode 100644 index 0000000..70f4118 --- /dev/null +++ b/modules/nixos/services/matrix/bridges.nix @@ -0,0 +1,143 @@ +# Matrix bridges for some services I use +{ config, lib, ... }: +let + cfg = config.my.services.matrix.bridges; + synapseCfg = config.services.matrix-synapse; + + domain = config.networking.domain; + serverName = synapseCfg.settings.server_name; + + mkBridgeOption = n: lib.mkEnableOption "${n} bridge" // { default = cfg.enable; }; + mkPortOption = n: default: lib.mkOption { + type = lib.types.port; + inherit default; + example = 8080; + description = "${n} bridge port"; + }; + mkEnvironmentFileOption = n: lib.mkOption { + type = lib.types.str; + example = "/run/secret/matrix/${lib.toLower n}-bridge-secrets.env"; + description = '' + Path to a file which should contain the secret values for ${n} bridge. + + Using through the following format: + + ``` + MATRIX_APPSERVICE_AS_TOKEN= + MATRIX_APPSERVICE_HS_TOKEN= + ``` + + Each bridge should use a different set of secrets, as they each register + their own independent double-puppetting appservice. + ''; + }; +in +{ + options.my.services.matrix.bridges = with lib; { + enable = mkEnableOption "bridges configuration"; + + admin = mkOption { + type = types.str; + default = "ambroisie"; + example = "admin"; + description = "Local username for the admin"; + }; + + facebook = { + enable = mkBridgeOption "Facebook"; + + port = mkPortOption "Facebook" 29321; + + environmentFile = mkEnvironmentFileOption "Facebook"; + }; + }; + + config = lib.mkMerge [ + (lib.mkIf cfg.facebook.enable { + services.mautrix-meta.instances.facebook = { + enable = true; + # Automatically register the bridge with synapse + registerToSynapse = true; + + # Provide `AS_TOKEN`, `HS_TOKEN` + inherit (cfg.facebook) environmentFile; + + settings = { + homeserver = { + domain = serverName; + address = "http://localhost:${toString config.my.services.matrix.port}"; + }; + + appservice = { + hostname = "localhost"; + inherit (cfg.facebook) port; + address = "http://localhost:${toString cfg.facebook.port}"; + public_address = "https://facebook-bridge.${domain}"; + + as_token = "$MATRIX_APPSERVICE_AS_TOKEN"; + hs_token = "$MATRIX_APPSERVICE_HS_TOKEN"; + + bot = { + username = "fbbot"; + }; + }; + + backfill = { + enabled = true; + }; + + bridge = { + delivery_receipts = true; + permissions = { + "*" = "relay"; + ${serverName} = "user"; + "@${cfg.admin}:${serverName}" = "admin"; + }; + }; + + database = { + type = "postgres"; + uri = "postgres:///mautrix-meta-facebook?host=/var/run/postgresql/"; + }; + + double_puppet = { + secrets = { + ${serverName} = "as_token:$MATRIX_APPSERVICE_AS_TOKEN"; + }; + }; + + network = { + # Don't be picky on Facebook/Messenger + allow_messenger_com_on_fb = true; + displayname_template = ''{{or .DisplayName .Username "Unknown user"}} (FB)''; + }; + + provisioning = { + shared_secret = "disable"; + }; + }; + }; + + services.postgresql = { + enable = true; + ensureDatabases = [ "mautrix-meta-facebook" ]; + ensureUsers = [{ + name = "mautrix-meta-facebook"; + ensureDBOwnership = true; + }]; + }; + + systemd.services.mautrix-meta-facebook = { + wants = [ "postgres.service" ]; + after = [ "postgres.service" ]; + }; + + my.services.nginx.virtualHosts = { + # Proxy to the bridge + "facebook-bridge" = { + inherit (cfg.facebook) port; + }; + }; + }) + ]; +} diff --git a/modules/nixos/services/matrix/default.nix b/modules/nixos/services/matrix/default.nix new file mode 100644 index 0000000..04d24a0 --- /dev/null +++ b/modules/nixos/services/matrix/default.nix @@ -0,0 +1,191 @@ +# Matrix homeserver setup. +{ config, lib, pkgs, ... }: + +let + cfg = config.my.services.matrix; + + adminPkg = pkgs.synapse-admin-etkecc; + + domain = config.networking.domain; + matrixDomain = "matrix.${domain}"; + + serverConfig = { + "m.server" = "${matrixDomain}:443"; + }; + clientConfig = { + "m.homeserver" = { + "base_url" = "https://${matrixDomain}"; + "server_name" = domain; + }; + "m.identity_server" = { + "base_url" = "https://vector.im"; + }; + }; + + # ACAO required to allow element-web on any URL to request this json file + mkWellKnown = data: '' + default_type application/json; + add_header Access-Control-Allow-Origin *; + return 200 '${builtins.toJSON data}'; + ''; +in +{ + imports = [ + ./bridges.nix + ]; + + options.my.services.matrix = with lib; { + enable = mkEnableOption "Matrix Synapse"; + + port = mkOption { + type = types.port; + default = 8448; + example = 8008; + description = "Internal port for listeners"; + }; + + 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; + initialScript = pkgs.writeText "synapse-init.sql" '' + CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD 'synapse'; + CREATE DATABASE "matrix-synapse" WITH OWNER "matrix-synapse" + TEMPLATE template0 + LC_COLLATE = "C" + LC_CTYPE = "C"; + ''; + }; + + services.matrix-synapse = { + enable = true; + dataDir = "/var/lib/matrix-synapse"; + + settings = { + server_name = domain; + public_baseurl = "https://${matrixDomain}"; + + enable_registration = false; + + listeners = [ + { + inherit (cfg) port; + bind_addresses = [ "::1" ]; + type = "http"; + tls = false; + x_forwarded = true; + resources = [ + { + names = [ "client" ]; + compress = true; + } + { + names = [ "federation" ]; + 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 + chat = { + root = pkgs.element-web.override { + conf = { + default_server_config = clientConfig; + show_labs_settings = true; + default_country_code = "FR"; # cocorico + room_directory = { + "servers" = [ + domain + "matrix.org" + "mozilla.org" + ]; + }; + }; + }; + }; + matrix = { + # Somewhat unused, but necessary for port collision detection + inherit (cfg) port; + + extraConfig = { + locations = { + # Or do a redirect instead of the 404, or whatever is appropriate + # for you. But do not put a Matrix Web client here! See the + # Element web section below. + "/".return = "404"; + + "/_matrix".proxyPass = "http://[::1]:${toString cfg.port}"; + "/_synapse".proxyPass = "http://[::1]:${toString cfg.port}"; + + "= /admin".return = "307 /admin/"; + "/admin/" = { + alias = "${adminPkg}/"; + priority = 500; + tryFiles = "$uri $uri/ /index.html"; + }; + "~ ^/admin/.*\\.(?:css|js|jpg|jpeg|gif|png|svg|ico|woff|woff2|ttf|eot|webp)$" = { + priority = 400; + root = adminPkg; + extraConfig = '' + rewrite ^/admin/(.*)$ /$1 break; + expires 30d; + more_set_headers "Cache-Control: public"; + ''; + }; + }; + }; + }; + }; + + # Those are too complicated to use my wrapper... + services.nginx.virtualHosts = { + "${domain}" = { + forceSSL = true; + useACMEHost = domain; + + locations."= /.well-known/matrix/server".extraConfig = mkWellKnown serverConfig; + locations."= /.well-known/matrix/client".extraConfig = mkWellKnown clientConfig; + }; + }; + + # For administration tools. + environment.systemPackages = [ pkgs.matrix-synapse ]; + + my.services.backup = { + paths = [ + config.services.matrix-synapse.dataDir + ]; + }; + }; +} diff --git a/modules/nixos/services/mealie/default.nix b/modules/nixos/services/mealie/default.nix new file mode 100644 index 0000000..8c02398 --- /dev/null +++ b/modules/nixos/services/mealie/default.nix @@ -0,0 +1,72 @@ +{ config, lib, ... }: +let + cfg = config.my.services.mealie; +in +{ + options.my.services.mealie = with lib; { + enable = mkEnableOption "Mealie service"; + + port = mkOption { + type = types.port; + default = 4537; + example = 8080; + description = "Internal port for webui"; + }; + + credentialsFile = mkOption { + type = types.str; + example = "/var/lib/mealie/credentials.env"; + description = '' + Configuration file for secrets. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + services.mealie = { + enable = true; + inherit (cfg) port credentialsFile; + + settings = { + # Basic settings + BASE_URL = "https://mealie.${config.networking.domain}"; + TZ = config.time.timeZone; + ALLOw_SIGNUP = "false"; + }; + + # Automatic PostgreSQL provisioning + database = { + createLocally = true; + }; + }; + + my.services.nginx.virtualHosts = { + mealie = { + inherit (cfg) port; + + extraConfig = { + # Allow bulk upload of recipes for import/export + locations."/".extraConfig = '' + client_max_body_size 0; + ''; + }; + }; + }; + + services.fail2ban.jails = { + mealie = '' + enabled = true + filter = mealie + port = http,https + ''; + }; + + environment.etc = { + "fail2ban/filter.d/mealie.conf".text = '' + [Definition] + failregex = ^.*ERROR.*Incorrect username or password from + journalmatch = _SYSTEMD_UNIT=mealie.service + ''; + }; + }; +} diff --git a/modules/nixos/services/miniflux/default.nix b/modules/nixos/services/miniflux/default.nix new file mode 100644 index 0000000..400ae00 --- /dev/null +++ b/modules/nixos/services/miniflux/default.nix @@ -0,0 +1,68 @@ +# 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 = { + reader = { + inherit (cfg) port; + }; + }; + + services.fail2ban.jails = { + miniflux = '' + enabled = true + filter = miniflux + port = http,https + ''; + }; + + environment.etc = { + "fail2ban/filter.d/miniflux.conf".text = '' + [Definition] + failregex = ^.*msg="[^"]*(Incorrect|Invalid) username or password[^"]*".*client_ip= + journalmatch = _SYSTEMD_UNIT=miniflux.service + ''; + }; + }; +} diff --git a/modules/nixos/services/monitoring/default.nix b/modules/nixos/services/monitoring/default.nix new file mode 100644 index 0000000..49919c1 --- /dev/null +++ b/modules/nixos/services/monitoring/default.nix @@ -0,0 +1,134 @@ +# 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 = { + monitoring = { + inherit (cfg.grafana) port; + }; + }; + }; +} diff --git a/modules/nixos/services/navidrome/default.nix b/modules/nixos/services/navidrome/default.nix new file mode 100644 index 0000000..c513b91 --- /dev/null +++ b/modules/nixos/services/navidrome/default.nix @@ -0,0 +1,72 @@ +# 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 = { + music = { + inherit (cfg) port; + }; + }; + + services.fail2ban.jails = { + navidrome = '' + enabled = true + filter = navidrome + port = http,https + ''; + }; + + environment.etc = { + "fail2ban/filter.d/navidrome.conf".text = '' + [Definition] + failregex = ^.*msg="Unsuccessful login".*X-Real-Ip:\[\] + journalmatch = _SYSTEMD_UNIT=navidrome.service + ''; + }; + }; +} diff --git a/modules/nixos/services/nextcloud/collabora.nix b/modules/nixos/services/nextcloud/collabora.nix new file mode 100644 index 0000000..f8f42a7 --- /dev/null +++ b/modules/nixos/services/nextcloud/collabora.nix @@ -0,0 +1,50 @@ +# Document editor with Nextcloud +{ config, lib, ... }: +let + cfg = config.my.services.nextcloud.collabora; +in +{ + options.my.services.nextcloud.collabora = with lib; { + enable = mkEnableOption "Collabora integration"; + + port = mkOption { + type = types.port; + default = 9980; + example = 8080; + description = "Internal port for API"; + }; + }; + + config = lib.mkIf cfg.enable { + services.collabora-online = { + enable = true; + inherit (cfg) port; + + aliasGroups = [ + { + host = "https://collabora.${config.networking.domain}"; + # Allow using from nextcloud + aliases = [ "https://${config.services.nextcloud.hostName}" ]; + } + ]; + + settings = { + # Rely on reverse proxy for SSL + ssl = { + enable = false; + termination = true; + }; + }; + }; + + my.services.nginx.virtualHosts = { + collabora = { + inherit (cfg) port; + websocketsLocations = [ + "~ ^/cool/(.*)/ws$" + "^~ /cool/adminws" + ]; + }; + }; + }; +} diff --git a/modules/nixos/services/nextcloud/default.nix b/modules/nixos/services/nextcloud/default.nix new file mode 100644 index 0000000..d8d4fce --- /dev/null +++ b/modules/nixos/services/nextcloud/default.nix @@ -0,0 +1,103 @@ +# A self-hosted cloud. +{ config, lib, pkgs, ... }: +let + cfg = config.my.services.nextcloud; +in +{ + imports = [ + ./collabora.nix + ]; + + options.my.services.nextcloud = with lib; { + enable = mkEnableOption "Nextcloud"; + maxSize = mkOption { + type = types.str; + default = "512M"; + example = "1G"; + description = "Maximum file upload size"; + }; + admin = mkOption { + type = types.str; + default = "Ambroisie"; + example = "admin"; + description = "Name of the admin user"; + }; + passwordFile = mkOption { + type = types.str; + 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.nextcloud31; + hostName = "nextcloud.${config.networking.domain}"; + home = "/var/lib/nextcloud"; + maxUploadSize = cfg.maxSize; + configureRedis = true; + config = { + adminuser = cfg.admin; + adminpassFile = cfg.passwordFile; + dbtype = "pgsql"; + }; + + https = true; + + # Automatic PostgreSQL provisioning + database = { + createLocally = true; + }; + + settings = { + 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; + }; + }; + + # The service above configures the domain, no need for my wrapper + services.nginx.virtualHosts."nextcloud.${config.networking.domain}" = { + forceSSL = true; + 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" + ]; + }; + + services.fail2ban.jails = { + nextcloud = '' + enabled = true + filter = nextcloud + port = http,https + ''; + }; + + environment.etc = { + "fail2ban/filter.d/nextcloud.conf".text = '' + [Definition] + _groupsre = (?:(?:,?\s*"\w+":(?:"[^"]+"|\w+))*) + datepattern = ,?\s*"time"\s*:\s*"%%Y-%%m-%%d[T ]%%H:%%M:%%S(%%z)?" + failregex = ^[^{]*\{%(_groupsre)s,?\s*"remoteAddr":""%(_groupsre)s,?\s*"message":"Login failed: + ^[^{]*\{%(_groupsre)s,?\s*"remoteAddr":""%(_groupsre)s,?\s*"message":"Trusted domain error. + ^[^{]*\{%(_groupsre)s,?\s*"remoteAddr":""%(_groupsre)s,?\s*"message":"Two-factor challenge failed: + journalmatch = _SYSTEMD_UNIT=phpfpm-nextcloud.service + ''; + }; + }; +} diff --git a/modules/nixos/services/nginx/default.nix b/modules/nixos/services/nginx/default.nix new file mode 100644 index 0000000..1e9e38a --- /dev/null +++ b/modules/nixos/services/nginx/default.nix @@ -0,0 +1,485 @@ +# 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 ({ name, ... }: { + options = { + subdomain = mkOption { + type = types.str; + default = name; + example = "dev"; + description = '' + Which subdomain, under config.networking.domain, to use + for this virtual host. + ''; + }; + + websocketsLocations = mkOption { + type = with types; listOf str; + default = [ ]; + example = [ "/socket" ]; + description = '' + Which locations on this virtual host should be configured for + websockets. + ''; + }; + + 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. + ''; + }; + + 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. + ''; + }; + + socket = mkOption { + type = with types; nullOr path; + default = null; + example = "FIXME"; + description = '' + The UNIX socket for this virtual host. + ''; + }; + + sso = { + enable = mkEnableOption "SSO authentication"; + }; + + extraConfig = mkOption { + type = types.attrs; # FIXME: forward type of virtualHosts + example = { + extraConfig = '' + add_header X-Clacks-Overhead "GNU Terry Pratchett"; + ''; + + locations."/".extraConfig = '' + client_max_body_size 1G; + ''; + }; + default = { }; + description = '' + Any extra configuration that should be applied to this virtual host. + ''; + }; + }; + }); +in +{ + options.my.services.nginx = with lib; { + enable = mkEnableOption "Nginx"; + + acme = { + credentialsFile = mkOption { + type = types.str; + example = "/var/lib/acme/creds.env"; + description = '' + OVH API key file as an 'EnvironmentFile' (see `systemd.exec(5)`) + ''; + }; + }; + + monitoring = { + enable = my.mkDisableOption "monitoring through grafana and prometheus"; + }; + + virtualHosts = mkOption { + type = types.attrsOf virtualHostOption; + default = { }; + example = { + gitea = { + subdomain = "git"; + port = 8080; + }; + dev = { + root = "/var/www/dev"; + }; + jellyfin = { + port = 8096; + websocketsLocations = [ "/socket" ]; + }; + }; + 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 = { + 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 = { + root = [ "alice" ]; + users = [ "alice" "bob" ]; + }; + description = "Groups of users"; + }; + }; + }; + + config = lib.mkIf cfg.enable { + assertions = [ ] + ++ (lib.flip lib.mapAttrsToList 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. + ''; + })) + ++ (lib.flip lib.mapAttrsToList cfg.virtualHosts (_: { subdomain, ... } @ args: + let + proxyPass = [ "port" "socket" ]; + proxyPassUsed = lib.any (v: args.${v} != null) proxyPass; + in + { + assertion = args.websocketsLocations != [ ] -> proxyPassUsed; + message = '' + Subdomain '${subdomain}' can only use 'websocketsLocations' with one of ${ + lib.concatStringsSep ", " (builtins.map (v: "'${v}'") proxyPass) + }. + ''; + })) + ++ ( + let + ports = lib.my.mapFilter + (v: v != null) + ({ port, ... }: port) + (lib.attrValues 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 = lib.mapAttrsToList (_: { 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; + + virtualHosts = + let + domain = config.networking.domain; + mkProxyPass = { websocketsLocations, ... }: proxyPass: + let + websockets = lib.genAttrs websocketsLocations (_: { + inherit proxyPass; + proxyWebsockets = true; + }); + in + { "/" = { inherit proxyPass; }; } // websockets; + 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 = mkProxyPass args "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 = mkProxyPass args "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 = + # FIXME: check that X-User is dropped otherwise + (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' (lib.attrValues 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 = { + ${cfg.sso.subdomain} = { + 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 = "ovh"; + dnsPropagationCheck = false; # OVH is slow + 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/nixos/services/nix-cache/default.nix b/modules/nixos/services/nix-cache/default.nix new file mode 100644 index 0000000..f3a29aa --- /dev/null +++ b/modules/nixos/services/nix-cache/default.nix @@ -0,0 +1,52 @@ +# Binary cache +{ config, lib, ... }: +let + cfg = config.my.services.nix-cache; +in +{ + options.my.services.nix-cache = with lib; { + enable = mkEnableOption "nix 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-cache"; + 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.harmonia = { + enable = true; + + settings = { + bind = "127.0.0.1:${toString cfg.port}"; + inherit (cfg) priority; + }; + + signKeyPaths = [ cfg.secretKeyFile ]; + }; + + my.services.nginx.virtualHosts = { + cache = { + inherit (cfg) port; + }; + }; + }; +} diff --git a/modules/nixos/services/paperless/default.nix b/modules/nixos/services/paperless/default.nix new file mode 100644 index 0000000..1195977 --- /dev/null +++ b/modules/nixos/services/paperless/default.nix @@ -0,0 +1,114 @@ +{ 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; + + settings = { + # Use SSO + PAPERLESS_ENABLE_HTTP_REMOTE_USER = true; + PAPERLESS_ENABLE_HTTP_REMOTE_USER_API = true; + PAPERLESS_HTTP_REMOTE_USER_HEADER_NAME = "HTTP_X_USER"; + + # Security settings + PAPERLESS_URL = "https://paperless.${config.networking.domain}"; + PAPERLESS_USE_X_FORWARD_HOST = true; + PAPERLESS_PROXY_SSL_HEADER = [ "HTTP_X_FORWARDED_PROTO" "https" ]; + + # OCR settings + PAPERLESS_OCR_LANGUAGE = "fra+eng"; + + # Workers + PAPERLESS_TASK_WORKERS = 3; + PAPERLESS_THREADS_PER_WORKER = 4; + + # Misc + PAPERLESS_TIME_ZONE = config.time.timeZone; + PAPERLESS_ADMIN_USER = cfg.username; + }; + + # Admin password + passwordFile = cfg.passwordFile; + + # Secret key + environmentFile = cfg.secretKeyFile; + + # Automatic PostgreSQL provisioning + database = { + createLocally = true; + }; + }; + + # Set-up media group + users.groups.media = { }; + + users.users.${config.services.paperless.user} = { + extraGroups = [ "media" ]; + }; + + my.services.nginx.virtualHosts = { + paperless = { + inherit (cfg) port; + sso = { + enable = true; + }; + websocketsLocations = [ "/" ]; + }; + }; + + my.services.backup = { + paths = [ + config.services.paperless.dataDir + config.services.paperless.mediaDir + ]; + }; + }; +} diff --git a/modules/nixos/services/pdf-edit/default.nix b/modules/nixos/services/pdf-edit/default.nix new file mode 100644 index 0000000..d59507b --- /dev/null +++ b/modules/nixos/services/pdf-edit/default.nix @@ -0,0 +1,73 @@ +{ config, lib, ... }: +let + cfg = config.my.services.pdf-edit; +in +{ + options.my.services.pdf-edit = with lib; { + enable = mkEnableOption "PDF edition service"; + + port = mkOption { + type = types.port; + default = 8089; + example = 8080; + description = "Internal port for webui"; + }; + + loginFile = mkOption { + type = types.str; + example = "/run/secrets/pdf-edit/login.env"; + description = '' + `SECURITY_INITIALLOGIN_USERNAME` and `SECURITY_INITIALLOGIN_PASSWORD` + defined in the format of 'EnvironmentFile' (see `systemd.exec(5)`). + ''; + }; + }; + + config = lib.mkIf cfg.enable { + services.stirling-pdf = lib.mkIf cfg.enable { + enable = true; + + environment = { + SERVER_PORT = cfg.port; + SECURITY_CSRFDISABLED = "false"; + + SYSTEM_SHOWUPDATE = "false"; # We don't care about update notifications + INSTALL_BOOK_AND_ADVANCED_HTML_OPS = "true"; # Installed by the module + + SECURITY_ENABLELOGIN = "true"; + SECURITY_LOGINATTEMPTCOUNT = "-1"; # Rely on fail2ban instead + }; + + environmentFiles = [ cfg.loginFile ]; + }; + + my.services.nginx.virtualHosts = { + pdf-edit = { + inherit (cfg) port; + + extraConfig = { + # Allow upload of PDF files up to 1G + locations."/".extraConfig = '' + client_max_body_size 1G; + ''; + }; + }; + }; + + services.fail2ban.jails = { + stirling-pdf = '' + enabled = true + filter = stirling-pdf + port = http,https + ''; + }; + + environment.etc = { + "fail2ban/filter.d/stirling-pdf.conf".text = '' + [Definition] + failregex = ^.*Failed login attempt from IP: $ + journalmatch = _SYSTEMD_UNIT=stirling-pdf.service + ''; + }; + }; +} diff --git a/modules/nixos/services/podgrab/default.nix b/modules/nixos/services/podgrab/default.nix new file mode 100644 index 0000000..3ced8d3 --- /dev/null +++ b/modules/nixos/services/podgrab/default.nix @@ -0,0 +1,55 @@ +# 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 authentication. + ''; + }; + + dataDir = mkOption { + type = with types; nullOr str; + default = null; + example = "/mnt/podgrab"; + description = '' + Path to the directory to store the podcasts. Use default if null + ''; + }; + + 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; + + group = "media"; + dataDirectory = lib.mkIf (cfg.dataDir != null) cfg.dataDir; + }; + + # Set-up media group + users.groups.media = { }; + + my.services.nginx.virtualHosts = { + podgrab = { + inherit (cfg) port; + }; + }; + }; +} diff --git a/services/postgresql-backup.nix b/modules/nixos/services/postgresql-backup/default.nix similarity index 100% rename from services/postgresql-backup.nix rename to modules/nixos/services/postgresql-backup/default.nix diff --git a/modules/nixos/services/postgresql/default.nix b/modules/nixos/services/postgresql/default.nix new file mode 100644 index 0000000..1dca164 --- /dev/null +++ b/modules/nixos/services/postgresql/default.nix @@ -0,0 +1,72 @@ +{ 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_17; + }; + }) + + # Taken from the manual + (lib.mkIf cfg.upgradeScript { + environment.systemPackages = + let + pgCfg = config.services.postgresql; + newPackage' = pkgs.postgresql_17; + + oldPackage = if pgCfg.enableJIT then pgCfg.package.withJIT else pgCfg.package; + oldData = pgCfg.dataDir; + oldBin = "${if pgCfg.extensions == [] then oldPackage else oldPackage.withPackages pgCfg.extensions}/bin"; + + newPackage = if pgCfg.enableJIT then newPackage'.withJIT else newPackage'; + newData = "/var/lib/postgresql/${newPackage.psqlSchema}"; + newBin = "${if pgCfg.extensions == [] then newPackage else newPackage.withPackages pgCfg.extensions}/bin"; + in + [ + (pkgs.writeScriptBin "upgrade-pg-cluster" '' + #!/usr/bin/env bash + + set -eux + export OLDDATA="${oldData}" + export NEWDATA="${newData}" + export OLDBIN="${oldBin}" + export NEWBIN="${newBin}" + + 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" \ + "$@" + + cat << EOF + Run the following commands after setting: + services.postgresql.package = pkgs.postgresql_${lib.versions.major newPackage.version} + sudo -u postgres vacuumdb --all --analyze-in-stages + ${newData}/delete_old_cluster.sh + EOF + '') + ]; + }) + ]; +} diff --git a/modules/nixos/services/pyload/default.nix b/modules/nixos/services/pyload/default.nix new file mode 100644 index 0000000..7257d0f --- /dev/null +++ b/modules/nixos/services/pyload/default.nix @@ -0,0 +1,72 @@ +{ config, lib, ... }: +let + cfg = config.my.services.pyload; +in +{ + options.my.services.pyload = with lib; { + enable = mkEnableOption "pyload download manager"; + + credentialsFile = mkOption { + type = types.path; + example = "/run/secrets/pyload-credentials.env"; + description = "pyload credentials"; + }; + + downloadDirectory = mkOption { + type = types.str; + default = "/data/downloads/pyload"; + example = "/var/lib/pyload/download"; + description = "Download directory"; + }; + + port = mkOption { + type = types.port; + default = 9093; + example = 8080; + description = "Internal port for webui"; + }; + }; + + config = lib.mkIf cfg.enable { + services.pyload = { + enable = true; + + # Listening on `localhost` leads to 502 with the reverse proxy... + listenAddress = "127.0.0.1"; + + inherit (cfg) + credentialsFile + downloadDirectory + port + ; + + # Use media group when downloading files + group = "media"; + }; + + # Set-up media group + users.groups.media = { }; + + my.services.nginx.virtualHosts = { + pyload = { + inherit (cfg) port; + }; + }; + + services.fail2ban.jails = { + pyload = '' + enabled = true + filter = pyload + port = http,https + ''; + }; + + environment.etc = { + "fail2ban/filter.d/pyload.conf".text = '' + [Definition] + failregex = ^.*Login failed for user '.*' \[CLIENT: \]$ + journalmatch = _SYSTEMD_UNIT=pyload.service + ''; + }; + }; +} diff --git a/services/quassel.nix b/modules/nixos/services/quassel/default.nix similarity index 95% rename from services/quassel.nix rename to modules/nixos/services/quassel/default.nix index ec686e1..695f9e0 100644 --- a/services/quassel.nix +++ b/modules/nixos/services/quassel/default.nix @@ -39,7 +39,7 @@ in ensureUsers = [ { name = "quassel"; - ensurePermissions."DATABASE quassel" = "ALL PRIVILEGES"; + ensureDBOwnership = true; } ]; # Insecure, I don't care. diff --git a/services/rss-bridge.nix b/modules/nixos/services/rss-bridge/default.nix similarity index 51% rename from services/rss-bridge.nix rename to modules/nixos/services/rss-bridge/default.nix index ad5141b..52b1030 100644 --- a/services/rss-bridge.nix +++ b/modules/nixos/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 = { @@ -13,13 +11,16 @@ in config = lib.mkIf cfg.enable { services.rss-bridge = { enable = true; - whitelist = [ "*" ]; # Whitelist all - virtualHost = rss-bridgeDomain; # Setup virtual host + config = { + system.enabled_bridges = [ "*" ]; # Whitelist all + }; + 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/nixos/services/sabnzbd/default.nix b/modules/nixos/services/sabnzbd/default.nix new file mode 100644 index 0000000..9e0d9c3 --- /dev/null +++ b/modules/nixos/services/sabnzbd/default.nix @@ -0,0 +1,56 @@ +# 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 = { + 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/modules/nixos/services/servarr/autobrr.nix b/modules/nixos/services/servarr/autobrr.nix new file mode 100644 index 0000000..c3370cb --- /dev/null +++ b/modules/nixos/services/servarr/autobrr.nix @@ -0,0 +1,63 @@ +# IRC-based indexer +{ config, lib, ... }: +let + cfg = config.my.services.servarr.autobrr; +in +{ + options.my.services.servarr.autobrr = with lib; { + enable = mkEnableOption "autobrr IRC announce tracker" // { + default = config.my.services.servarr.enableAll; + }; + + port = mkOption { + type = types.port; + default = 7474; + example = 8080; + description = "Internal port for webui"; + }; + + sessionSecretFile = mkOption { + type = types.str; + example = "/run/secrets/autobrr-secret.txt"; + description = '' + File containing the session secret. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + services.autobrr = { + enable = true; + + settings = { + inherit (cfg) port; + checkForUpdates = false; + }; + + secretFile = cfg.sessionSecretFile; + }; + + my.services.nginx.virtualHosts = { + autobrr = { + inherit (cfg) port; + websocketsLocations = [ "/api" ]; + }; + }; + + services.fail2ban.jails = { + autobrr = '' + enabled = true + filter = autobrr + action = iptables-allports + ''; + }; + + environment.etc = { + "fail2ban/filter.d/autobrr.conf".text = '' + [Definition] + failregex = "message":"Auth: Failed login attempt username: \[.*\] ip: " + journalmatch = _SYSTEMD_UNIT=autobrr.service + ''; + }; + }; +} diff --git a/modules/nixos/services/servarr/bazarr.nix b/modules/nixos/services/servarr/bazarr.nix new file mode 100644 index 0000000..637da0c --- /dev/null +++ b/modules/nixos/services/servarr/bazarr.nix @@ -0,0 +1,37 @@ +{ config, lib, ... }: +let + cfg = config.my.services.servarr.bazarr; +in +{ + options.my.services.servarr.bazarr = with lib; { + enable = lib.mkEnableOption "Bazarr" // { + default = config.my.services.servarr.enableAll; + }; + + port = mkOption { + type = types.port; + default = 6767; + example = 8080; + description = "Internal port for webui"; + }; + }; + + config = lib.mkIf cfg.enable { + services.bazarr = { + enable = true; + group = "media"; + listenPort = cfg.port; + }; + + # Set-up media group + users.groups.media = { }; + + my.services.nginx.virtualHosts = { + bazarr = { + inherit (cfg) port; + }; + }; + + # Bazarr does not log authentication failures... + }; +} diff --git a/modules/nixos/services/servarr/cross-seed.nix b/modules/nixos/services/servarr/cross-seed.nix new file mode 100644 index 0000000..74f216a --- /dev/null +++ b/modules/nixos/services/servarr/cross-seed.nix @@ -0,0 +1,96 @@ +# Automatic cross-seeding for video media +{ config, lib, ... }: +let + cfg = config.my.services.servarr.cross-seed; +in +{ + options.my.services.servarr.cross-seed = with lib; { + enable = mkEnableOption "cross-seed daemon" // { + default = config.my.services.servarr.enableAll; + }; + + port = mkOption { + type = types.port; + default = 2468; + example = 8080; + description = "Internal port for daemon"; + }; + + linkDirectory = mkOption { + type = types.str; + default = "/data/downloads/complete/links"; + example = "/var/lib/cross-seed/links"; + description = "Link directory"; + }; + + secretSettingsFile = mkOption { + type = types.str; + example = "/run/secrets/cross-seed-secrets.json"; + description = '' + File containing secret settings. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + services.cross-seed = { + enable = true; + group = "media"; + + # Rely on recommended defaults for tracker snatches etc... + useGenConfigDefaults = true; + + settings = { + inherit (cfg) port; + host = "127.0.0.1"; + + # Inject torrents to client directly + action = "inject"; + # Query the client for torrents to match + useClientTorrents = true; + # Use hardlinks + linkType = "hardlink"; + # Use configured link directory + linkDirs = [ cfg.linkDirectory ]; + # Match as many torrents as possible + matchMode = "partial"; + # Cross-seed full season if at least 50% of episodes are already downloaded + seasonFromEpisodes = 0.5; + }; + + settingsFile = cfg.secretSettingsFile; + }; + + systemd.services.cross-seed = { + serviceConfig = { + # Loose umask to make cross-seed links readable by `media` + UMask = "0002"; + }; + }; + + # Set-up media group + users.groups.media = { }; + + my.services.nginx.virtualHosts = { + cross-seed = { + inherit (cfg) port; + }; + }; + + services.fail2ban.jails = { + cross-seed = '' + enabled = true + filter = cross-seed + action = iptables-allports + ''; + }; + + environment.etc = { + "fail2ban/filter.d/cross-seed.conf".text = '' + [Definition] + failregex = ^.*Unauthorized API access attempt to .* from $ + journalmatch = _SYSTEMD_UNIT=cross-seed.service + ''; + }; + }; +} diff --git a/modules/nixos/services/servarr/default.nix b/modules/nixos/services/servarr/default.nix new file mode 100644 index 0000000..dca57cf --- /dev/null +++ b/modules/nixos/services/servarr/default.nix @@ -0,0 +1,23 @@ +# The total autonomous media delivery system. +# Relevant link [1]. +# +# [1]: https://youtu.be/I26Ql-uX6AM +{ lib, ... }: +{ + imports = [ + ./autobrr.nix + ./bazarr.nix + ./cross-seed.nix + ./jackett.nix + ./nzbhydra.nix + ./prowlarr.nix + (import ./starr.nix "lidarr") + (import ./starr.nix "radarr") + (import ./starr.nix "readarr") + (import ./starr.nix "sonarr") + ]; + + options.my.services.servarr = { + enableAll = lib.mkEnableOption "media automation suite"; + }; +} diff --git a/modules/nixos/services/servarr/jackett.nix b/modules/nixos/services/servarr/jackett.nix new file mode 100644 index 0000000..481cd3d --- /dev/null +++ b/modules/nixos/services/servarr/jackett.nix @@ -0,0 +1,41 @@ +{ config, lib, ... }: +let + cfg = config.my.services.servarr.jackett; +in +{ + options.my.services.servarr.jackett = with lib; { + enable = lib.mkEnableOption "Jackett" // { + default = config.my.services.servarr.enableAll; + }; + + port = mkOption { + type = types.port; + default = 9117; + example = 8080; + description = "Internal port for webui"; + }; + }; + + config = lib.mkIf cfg.enable { + services.jackett = { + enable = true; + inherit (cfg) port; + }; + + # 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 = { + jackett = { + inherit (cfg) port; + }; + }; + + # Jackett does not log authentication failures... + }; +} diff --git a/modules/nixos/services/servarr/nzbhydra.nix b/modules/nixos/services/servarr/nzbhydra.nix new file mode 100644 index 0000000..7b63986 --- /dev/null +++ b/modules/nixos/services/servarr/nzbhydra.nix @@ -0,0 +1,26 @@ +{ config, lib, ... }: +let + cfg = config.my.services.servarr.nzbhydra; +in +{ + options.my.services.servarr.nzbhydra = with lib; { + enable = lib.mkEnableOption "NZBHydra2" // { + default = config.my.services.servarr.enableAll; + }; + }; + + config = lib.mkIf cfg.enable { + services.nzbhydra2 = { + enable = true; + }; + + my.services.nginx.virtualHosts = { + nzbhydra = { + port = 5076; + websocketsLocations = [ "/" ]; + }; + }; + + # NZBHydra2 does not log authentication failures... + }; +} diff --git a/modules/nixos/services/servarr/prowlarr.nix b/modules/nixos/services/servarr/prowlarr.nix new file mode 100644 index 0000000..ce044c6 --- /dev/null +++ b/modules/nixos/services/servarr/prowlarr.nix @@ -0,0 +1,53 @@ +# Torrent and NZB indexer +{ config, lib, ... }: +let + cfg = config.my.services.servarr.prowlarr; +in +{ + options.my.services.servarr.prowlarr = with lib; { + enable = lib.mkEnableOption "Prowlarr" // { + default = config.my.services.servarr.enableAll; + }; + + port = mkOption { + type = types.port; + default = 9696; + example = 8080; + description = "Internal port for webui"; + }; + }; + + config = lib.mkIf cfg.enable { + services.prowlarr = { + enable = true; + + settings = { + server = { + port = cfg.port; + }; + }; + }; + + my.services.nginx.virtualHosts = { + prowlarr = { + inherit (cfg) port; + }; + }; + + services.fail2ban.jails = { + prowlarr = '' + enabled = true + filter = prowlarr + action = iptables-allports + ''; + }; + + environment.etc = { + "fail2ban/filter.d/prowlarr.conf".text = '' + [Definition] + failregex = ^.*\|Warn\|Auth\|Auth-Failure ip username .*$ + journalmatch = _SYSTEMD_UNIT=prowlarr.service + ''; + }; + }; +} diff --git a/modules/nixos/services/servarr/starr.nix b/modules/nixos/services/servarr/starr.nix new file mode 100644 index 0000000..2bf7c11 --- /dev/null +++ b/modules/nixos/services/servarr/starr.nix @@ -0,0 +1,64 @@ +# Templated *arr configuration +starr: +{ config, lib, ... }: +let + cfg = config.my.services.servarr.${starr}; + ports = { + lidarr = 8686; + radarr = 7878; + readarr = 8787; + sonarr = 8989; + }; +in +{ + options.my.services.servarr.${starr} = with lib; { + enable = lib.mkEnableOption (lib.toSentenceCase starr) // { + default = config.my.services.servarr.enableAll; + }; + + port = mkOption { + type = types.port; + default = ports.${starr}; + example = 8080; + description = "Internal port for webui"; + }; + }; + + config = lib.mkIf cfg.enable { + services.${starr} = { + enable = true; + group = "media"; + + settings = { + server = { + port = cfg.port; + }; + }; + }; + + # Set-up media group + users.groups.media = { }; + + my.services.nginx.virtualHosts = { + ${starr} = { + port = cfg.port; + }; + }; + + services.fail2ban.jails = { + ${starr} = '' + enabled = true + filter = ${starr} + action = iptables-allports + ''; + }; + + environment.etc = { + "fail2ban/filter.d/${starr}.conf".text = '' + [Definition] + failregex = ^.*\|Warn\|Auth\|Auth-Failure ip username .*$ + journalmatch = _SYSTEMD_UNIT=${starr}.service + ''; + }; + }; +} diff --git a/services/ssh-server.nix b/modules/nixos/services/ssh-server/default.nix similarity index 76% rename from services/ssh-server.nix rename to modules/nixos/services/ssh-server/default.nix index a41a673..9ae0fa8 100644 --- a/services/ssh-server.nix +++ b/modules/nixos/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/nixos/services/tandoor-recipes/default.nix b/modules/nixos/services/tandoor-recipes/default.nix new file mode 100644 index 0000000..4b4ed1a --- /dev/null +++ b/modules/nixos/services/tandoor-recipes/default.nix @@ -0,0 +1,71 @@ +{ 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; + + database = { + createLocally = true; + }; + + port = cfg.port; + extraConfig = + let + tandoorRecipesDomain = "recipes.${config.networking.domain}"; + in + { + # Security settings + ALLOWED_HOSTS = tandoorRecipesDomain; + CSRF_TRUSTED_ORIGINS = "https://${tandoorRecipesDomain}"; + + # Misc + TIMEZONE = config.time.timeZone; + }; + }; + + systemd.services = { + tandoor-recipes = { + serviceConfig = { + EnvironmentFile = cfg.secretKeyFile; + }; + }; + }; + + my.services.nginx.virtualHosts = { + recipes = { + inherit (cfg) port; + + extraConfig = { + # Allow bulk upload of recipes for import/export + locations."/".extraConfig = '' + client_max_body_size 0; + ''; + }; + }; + }; + + # NOTE: unfortunately tandoor-recipes does not log connection failures for fail2ban + }; +} diff --git a/modules/nixos/services/thelounge/default.nix b/modules/nixos/services/thelounge/default.nix new file mode 100644 index 0000000..e224839 --- /dev/null +++ b/modules/nixos/services/thelounge/default.nix @@ -0,0 +1,59 @@ +# Web IRC client +{ config, lib, ... }: +let + cfg = config.my.services.thelounge; +in +{ + options.my.services.thelounge = with lib; { + enable = mkEnableOption "The Lounge, a self-hosted web IRC client"; + + port = mkOption { + type = types.port; + default = 9050; + example = 4242; + description = "The port on which The Lounge will listen for incoming HTTP traffic."; + }; + }; + + config = lib.mkIf cfg.enable { + services.thelounge = { + enable = true; + inherit (cfg) port; + + extraConfig = { + reverseProxy = true; + }; + }; + + my.services.nginx.virtualHosts = { + irc = { + inherit (cfg) port; + # Proxy websockets for RPC + websocketsLocations = [ "/" ]; + + extraConfig = { + locations."/".extraConfig = '' + proxy_read_timeout 1d; + ''; + }; + }; + }; + + services.fail2ban.jails = { + thelounge = '' + enabled = true + filter = thelounge + port = http,https + ''; + }; + + environment.etc = { + "fail2ban/filter.d/thelounge.conf".text = '' + [Definition] + failregex = Authentication failed for user .* from $ + Authentication for non existing user attempted from $ + journalmatch = _SYSTEMD_UNIT=thelounge.service + ''; + }; + }; +} diff --git a/modules/nixos/services/tlp/default.nix b/modules/nixos/services/tlp/default.nix new file mode 100644 index 0000000..8c9edd6 --- /dev/null +++ b/modules/nixos/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/nixos/services/transmission/default.nix similarity index 63% rename from services/transmission.nix rename to modules/nixos/services/transmission/default.nix index 29e181b..ddd77d4 100644 --- a/services/transmission.nix +++ b/modules/nixos/services/transmission/default.nix @@ -3,28 +3,21 @@ # Inspired by [1] # # [1]: https://github.com/delroth/infra.delroth.net/blob/master/roles/seedbox.nix -{ config, lib, ... }: +{ config, lib, pkgs, ... }: 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; @@ -52,10 +45,14 @@ in config = lib.mkIf cfg.enable { services.transmission = { enable = true; + package = pkgs.transmission_4; group = "media"; + webHome = pkgs.trgui-ng-web; downloadDirPermissions = "775"; + inherit (cfg) credentialsFile; + settings = { download-dir = "${cfg.downloadBase}/complete"; incomplete-dir = "${cfg.downloadBase}/incomplete"; @@ -63,30 +60,40 @@ 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"; + + umask = "002"; # To go with `downloadDirPermissions` }; }; + # 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 = { + transmission = { + inherit (cfg) port; + }; }; networking.firewall = { allowedTCPPorts = [ cfg.peerPort ]; allowedUDPPorts = [ cfg.peerPort ]; }; + + # NOTE: unfortunately transmission does not log connection failures for fail2ban }; } diff --git a/modules/nixos/services/vikunja/default.nix b/modules/nixos/services/vikunja/default.nix new file mode 100644 index 0000000..2753da3 --- /dev/null +++ b/modules/nixos/services/vikunja/default.nix @@ -0,0 +1,105 @@ +# 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; + + database = { + type = "postgres"; + user = "vikunja"; + database = "vikunja"; + host = "/run/postgresql"; + }; + + settings = { + service = { + # Only allow registration of users through the CLI + enableregistration = false; + # Use 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 = { + ${subdomain} = { + socket = socketPath; + }; + }; + + systemd.services.vikunja = { + 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"; + ensureDBOwnership = true; + } + ]; + }; + + my.services.backup = { + paths = [ + config.services.vikunja.settings.files.basepath + ]; + }; + + # NOTE: unfortunately vikunja does not log connection failures for fail2ban + }; +} diff --git a/services/wireguard.nix b/modules/nixos/services/wireguard/default.nix similarity index 78% rename from services/wireguard.nix rename to modules/nixos/services/wireguard/default.nix index fc948f6..840ac33 100644 --- a/services/wireguard.nix +++ b/modules/nixos/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 = "37.187.146.15"; + }; + + # "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 @@ -76,6 +100,8 @@ in options.my.services.wireguard = with lib; { enable = mkEnableOption "Wireguard VPN service"; + simpleManagement = my.mkDisableOption "manage units without password prompts"; + startAtBoot = mkEnableOption '' Should the VPN service be started at boot. Must be true for the server to work reliably. @@ -180,7 +206,7 @@ in ]; } - # Additional inteface is only used to get access to "LAN" from wireguard + # Additional interface is only used to get access to "LAN" from wireguard (lib.mkIf cfg.internal.enable { networking.wg-quick.interfaces."${cfg.internal.name}" = mkInterface [ "${cfg.net.v4.subnet}.0/${toString cfg.net.v4.mask}" @@ -237,5 +263,36 @@ in (lib.mkIf (cfg.internal.enable && !cfg.internal.startAtBoot) { systemd.services."wg-quick-${cfg.internal.name}".wantedBy = lib.mkForce [ ]; }) + + # Make systemd shut down one service when starting the other + (lib.mkIf (cfg.internal.enable) { + systemd.services."wg-quick-${cfg.iface}" = { + conflicts = [ "wg-quick-${cfg.internal.name}.service" ]; + after = [ "wg-quick-${cfg.internal.name}.service" ]; + }; + systemd.services."wg-quick-${cfg.internal.name}" = { + conflicts = [ "wg-quick-${cfg.iface}.service" ]; + after = [ "wg-quick-${cfg.iface}.service" ]; + }; + }) + + # Make it possible to manage those units without using passwords, for admins + (lib.mkIf cfg.simpleManagement { + environment.etc."polkit-1/rules.d/50-wg-quick.rules".text = '' + polkit.addRule(function(action, subject) { + if (action.id == "org.freedesktop.systemd1.manage-units") { + var unit = action.lookup("unit") + if (unit == "wg-quick-${cfg.iface}.service" || unit == "wg-quick-${cfg.internal.name}.service") { + var verb = action.lookup("verb"); + if (verb == "start" || verb == "stop" || verb == "restart") { + if (subject.isInGroup("wheel")) { + return polkit.Result.YES; + } + } + } + } + }); + ''; + }) ]); } diff --git a/modules/nixos/services/wireguard/keys/milady/private-key.age b/modules/nixos/services/wireguard/keys/milady/private-key.age new file mode 100644 index 0000000..fb84f91 --- /dev/null +++ b/modules/nixos/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`Wm!Q3]Q}}By kuƀE^zO[V p f>Ĕ \ No newline at end of file diff --git a/modules/nixos/services/wireguard/keys/richelieu/private-key.age b/modules/nixos/services/wireguard/keys/richelieu/private-key.age new file mode 100644 index 0000000..e796688 --- /dev/null +++ b/modules/nixos/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_{RLX`Wͻx*Pr`UpJɔF#YXPS s \ No newline at end of file diff --git a/modules/nixos/services/wireguard/keys/secrets.nix b/modules/nixos/services/wireguard/keys/secrets.nix new file mode 100644 index 0000000..3985477 --- /dev/null +++ b/modules/nixos/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/nixos/services/woodpecker/agent-docker/default.nix b/modules/nixos/services/woodpecker/agent-docker/default.nix new file mode 100644 index 0000000..79d3299 --- /dev/null +++ b/modules/nixos/services/woodpecker/agent-docker/default.nix @@ -0,0 +1,42 @@ +{ 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; + + # 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/nixos/services/woodpecker/agent-exec/default.nix b/modules/nixos/services/woodpecker/agent-exec/default.nix new file mode 100644 index 0000000..24161b0 --- /dev/null +++ b/modules/nixos/services/woodpecker/agent-exec/default.nix @@ -0,0 +1,66 @@ +{ 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"; + }; + + path = with pkgs; [ + woodpecker-plugin-git + bash + coreutils + git + git-lfs + gnutar + gzip + nix + ]; + + environmentFile = [ cfg.sharedSecretFile ]; + }; + }; + + # Adjust runner service for nix usage + systemd.services.woodpecker-agent-exec = { + # Might break deployment + restartIfChanged = false; + + serviceConfig = { + # Same option as upstream, without @setuid + SystemCallFilter = lib.mkForce "~@clock @privileged @cpu-emulation @debug @keyring @module @mount @obsolete @raw-io @reboot @swap"; + # NodeJS requires RWX memory... + MemoryDenyWriteExecute = lib.mkForce false; + + 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/nixos/services/woodpecker/default.nix b/modules/nixos/services/woodpecker/default.nix new file mode 100644 index 0000000..012eaae --- /dev/null +++ b/modules/nixos/services/woodpecker/default.nix @@ -0,0 +1,52 @@ +{ lib, ... }: +{ + imports = [ + ./agent-docker + ./agent-exec + ./server + ]; + + options.my.services.woodpecker = with lib; { + enable = mkEnableOption "Woodpecker CI"; + forge = mkOption { + type = types.enum [ "gitea" "forgejo" ]; + default = "forgejo"; + example = "gitea"; + description = "Which Forge to connect to"; + }; + 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/nixos/services/woodpecker/server/default.nix b/modules/nixos/services/woodpecker/server/default.nix new file mode 100644 index 0000000..caf0179 --- /dev/null +++ b/modules/nixos/services/woodpecker/server/default.nix @@ -0,0 +1,65 @@ +{ 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.${cfg.forge}.settings.server.ROOT_URL; + + WOODPECKER_LOG_LEVEL = "debug"; + }; + }; + + systemd.services.woodpecker-server = { + after = [ "postgresql.target" ]; + requires = [ "postgresql.target" ]; + + 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"; + ensureDBOwnership = true; + }]; + }; + + my.services.nginx.virtualHosts = { + woodpecker = { + inherit (cfg) port; + }; + # I might want to be able to RPC from other hosts in the future + woodpecker-rpc = { + port = cfg.rpcPort; + }; + }; + }; +} diff --git a/modules/nixos/system/boot/default.nix b/modules/nixos/system/boot/default.nix new file mode 100644 index 0000000..3d8495e --- /dev/null +++ b/modules/nixos/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/nixos/system/default.nix b/modules/nixos/system/default.nix new file mode 100644 index 0000000..e6fb25b --- /dev/null +++ b/modules/nixos/system/default.nix @@ -0,0 +1,17 @@ +# System-related modules +{ ... }: + +{ + imports = [ + ./boot + ./docker + ./documentation + ./language + ./nix + ./packages + ./podman + ./polkit + ./printing + ./users + ]; +} diff --git a/modules/nixos/system/docker/default.nix b/modules/nixos/system/docker/default.nix new file mode 100644 index 0000000..f051814 --- /dev/null +++ b/modules/nixos/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/nixos/system/documentation/default.nix b/modules/nixos/system/documentation/default.nix new file mode 100644 index 0000000..304c811 --- /dev/null +++ b/modules/nixos/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/nixos/system/language/default.nix b/modules/nixos/system/language/default.nix new file mode 100644 index 0000000..f2bbcde --- /dev/null +++ b/modules/nixos/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/nixos/system/nix/default.nix b/modules/nixos/system/nix/default.nix new file mode 100644 index 0000000..12a395e --- /dev/null +++ b/modules/nixos/system/nix/default.nix @@ -0,0 +1,129 @@ +# 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"; + + gc = { + enable = my.mkDisableOption "nix GC 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" ]; + # Trusted users are equivalent to root, and might as well allow wheel + trusted-users = [ "root" "@wheel" ]; + }; + }; + } + + (lib.mkIf cfg.gc.enable { + nix.gc = { + automatic = true; + + # Every week, with some wiggle room + dates = "weekly"; + randomizedDelaySec = "10min"; + + # Use a persistent timer for e.g: laptops + persistent = true; + + # Delete old profiles automatically after 15 days + options = "--delete-older-than 15d"; + }; + }) + + (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/nixos/system/packages/default.nix b/modules/nixos/system/packages/default.nix new file mode 100644 index 0000000..6a78ff6 --- /dev/null +++ b/modules/nixos/system/packages/default.nix @@ -0,0 +1,33 @@ +# Common packages +{ config, lib, ... }: +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 { + programs = { + vim = { + enable = true; + 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/nixos/system/podman/default.nix b/modules/nixos/system/podman/default.nix new file mode 100644 index 0000000..52630c7 --- /dev/null +++ b/modules/nixos/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/nixos/system/polkit/default.nix b/modules/nixos/system/polkit/default.nix new file mode 100644 index 0000000..1e5b573 --- /dev/null +++ b/modules/nixos/system/polkit/default.nix @@ -0,0 +1,16 @@ +# Polkit settings +{ config, lib, ... }: +let + cfg = config.my.system.polkit; +in +{ + options.my.system.polkit = with lib; { + enable = my.mkDisableOption "polkit configuration"; + }; + + config = lib.mkIf cfg.enable { + security.polkit = { + enable = true; + }; + }; +} diff --git a/modules/nixos/system/printing/default.nix b/modules/nixos/system/printing/default.nix new file mode 100644 index 0000000..0dfab0f --- /dev/null +++ b/modules/nixos/system/printing/default.nix @@ -0,0 +1,69 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.system.printing; +in +{ + options.my.system.printing = with lib; { + enable = mkEnableOption "printing configuration"; + + 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 + nssmdns4 = true; + }; + }; +} diff --git a/modules/nixos/system/users/default.nix b/modules/nixos/system/users/default.nix new file mode 100644 index 0000000..655b31e --- /dev/null +++ b/modules/nixos/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 = { + hashedPasswordFile = secrets."users/root/hashed-password".path; + }; + + ${config.my.user.name} = { + hashedPasswordFile = 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/nixos/system/users/ssh/aramis.pub similarity index 100% rename from modules/ssh/aramis.pub rename to modules/nixos/system/users/ssh/aramis.pub diff --git a/modules/ssh/shared.pub b/modules/nixos/system/users/ssh/shared.pub similarity index 100% rename from modules/ssh/shared.pub rename to modules/nixos/system/users/ssh/shared.pub 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/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/overlays/downgrade-transmission/default.nix b/overlays/downgrade-transmission/default.nix new file mode 100644 index 0000000..9d3fc8a --- /dev/null +++ b/overlays/downgrade-transmission/default.nix @@ -0,0 +1,14 @@ +self: prev: +{ + transmission_4 = prev.transmission_4.overrideAttrs (_: { + version = "4.0.5"; + + src = self.fetchFromGitHub { + owner = "transmission"; + repo = "transmission"; + rev = "4.0.5"; + hash = "sha256-gd1LGAhMuSyC/19wxkoE2mqVozjGPfupIPGojKY0Hn4="; + fetchSubmodules = true; + }; + }); +} diff --git a/overlays/gruvbox-nvin-expose-palette/default.nix b/overlays/gruvbox-nvin-expose-palette/default.nix new file mode 100644 index 0000000..832e71d --- /dev/null +++ b/overlays/gruvbox-nvin-expose-palette/default.nix @@ -0,0 +1,4 @@ +self: prev: +{ + vimPlugins = prev.vimPlugins.extend (self.callPackage ./generated.nix { }); +} diff --git a/overlays/gruvbox-nvin-expose-palette/generated.nix b/overlays/gruvbox-nvin-expose-palette/generated.nix new file mode 100644 index 0000000..c52ad04 --- /dev/null +++ b/overlays/gruvbox-nvin-expose-palette/generated.nix @@ -0,0 +1,14 @@ +{ fetchpatch, ... }: + +_final: prev: { + gruvbox-nvim = prev.gruvbox-nvim.overrideAttrs (oa: { + patches = (oa.patches or [ ]) ++ [ + # https://github.com/ellisonleao/gruvbox.nvim/pull/319 + (fetchpatch { + name = "expose-color-palette.patch"; + url = "https://github.com/ellisonleao/gruvbox.nvim/commit/07a493ba4f8b650aab9ed9e486caa89822be0996.patch"; + hash = "sha256-iGwt8qIHe2vaiAUcpaUxyGlM472F89vobTdQ7CF/H70="; + }) + ]; + }); +} diff --git a/pkgs/bw-pass/bw-pass b/pkgs/bw-pass/bw-pass new file mode 100755 index 0000000..0e974b7 --- /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..5a1fb9c --- /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..857b9c9 --- /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 --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_NIXPKGS_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..6b7fce1 100644 --- a/pkgs/default.nix +++ b/pkgs/default.nix @@ -1,18 +1,32 @@ { 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 { }; + + 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; - }; + osc777 = pkgs.callPackage ./osc777 { }; - unified-hosts-lists = pkgs.callPackage ./unified-hosts-lists { }; -} + rbw-pass = pkgs.callPackage ./rbw-pass { }; + + unbound-zones-adblock = pkgs.callPackage ./unbound-zones-adblock { }; + + zsh-done = pkgs.callPackage ./zsh-done { }; +}) 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..a2a3513 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() { @@ -75,26 +81,34 @@ parse_args() { shift case "$opt" in - -h|--help) + -h | --help) usage exit ;; - -f|--flake-output) + -f | --flake-output) FLAKE_OUTPUTS+=("$1") shift ;; - -o|--output) + -o | --output) OUTPUT_FILE="$1" shift ;; - -n|--new-rev) + -n | --new-rev) NEW_REV="$(git rev-parse "$1")" shift ;; - -p|--previous-rev) + -p | --previous-rev) 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,14 +138,26 @@ 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; + local PREV NEW PREV="$(mktemp --dry-run)" NEW="$(mktemp --dry-run)" @@ -143,16 +169,21 @@ diff_output() { printf 'Closure diff for `%s`:\n```\n' "$1" nix store diff-closures "$PREV" "$NEW" | sanitize_output printf '```\n\n' - } >> "$OUTPUT_FILE" + } >>"$OUTPUT_FILE" } 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/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..aeb13b1 100644 --- a/pkgs/lohr/default.nix +++ b/pkgs/lohr/default.nix @@ -1,21 +1,24 @@ -{ fetchFromGitHub, lib, rustPlatform }: +{ lib, fetchFromGitHub, rustPlatform }: rustPlatform.buildRustPackage rec { pname = "lohr"; - version = "0.4.0"; + version = "0.4.6"; src = fetchFromGitHub { owner = "alarsyo"; repo = "lohr"; rev = "v${version}"; - sha256 = "sha256-MplTVJG+SoeLMXQP+ix/zM3OSHuQmZnunn900YnyCBw="; + hash = "sha256-dunQgtap+XCK5LoSyOqIY/6p6HizBeiyPWNuCffwjDU="; }; - cargoSha256 = "sha256-iuMJj8tqetlmdfsrfudnU1afwUzjls/UdYLq1u0gr+g="; + useFetchCargoVendor = true; + cargoHash = "sha256-R3/N/43+bGx6acE/rhBcrk6kS5zQu8NJ1sVvKJJkK9w="; 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..aba093f --- /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.4.0"; + + src = fetchFromGitHub { + owner = "ambroisie"; + repo = "matrix-notifier"; + rev = "v${version}"; + hash = "sha256-6KHteQx0bHodpNp7cuUIGM7uBRPaj386n2t5yz6umpY="; + }; + + 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..de3a982 --- /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 < 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/osc777/default.nix b/pkgs/osc777/default.nix new file mode 100644 index 0000000..6e9ce2e --- /dev/null +++ b/pkgs/osc777/default.nix @@ -0,0 +1,41 @@ +{ lib, coreutils, makeWrapper, stdenvNoCC }: +stdenvNoCC.mkDerivation rec { + pname = "osc777"; + version = "0.1.0"; + + src = ./osc777; + + nativeBuildInputs = [ + makeWrapper + ]; + + dontUnpack = true; + + dontBuild = true; + + installPhase = '' + mkdir -p $out/bin + cp $src $out/bin/osc777 + chmod a+x $out/bin/osc777 + ''; + + wrapperPath = lib.makeBinPath [ + coreutils + ]; + + fixupPhase = '' + patchShebangs $out/bin/osc777 + wrapProgram $out/bin/osc777 --prefix PATH : "${wrapperPath}" + ''; + + meta = with lib; { + description = '' + A script to send notifications using the OSC777 escape sequence + ''; + homepage = "https://git.belanyi.fr/ambroisie/nix-config"; + license = with licenses; [ mit ]; + mainProgram = "osc777"; + maintainers = with maintainers; [ ambroisie ]; + platforms = platforms.linux; + }; +} diff --git a/pkgs/osc777/osc777 b/pkgs/osc777/osc777 new file mode 100755 index 0000000..9bcac03 --- /dev/null +++ b/pkgs/osc777/osc777 @@ -0,0 +1,126 @@ +#!/usr/bin/env bash + +# Whether to disable tmux/screen DCS escape sequences +: "${OSC777_NO_DCS:=0}" + +die() { + echo "ERROR: $*" + exit 1 +} + +usage() { + if [ $# -gt 0 ]; then + exec 1>&2 + fi + + cat < +Send a notification (title and message) to the host system using the OSC 777 +escape sequence: +Options: + -h, --help This screen. + -d, --no-dcs Disable tmux/screen specific DCS sequences, only use OSC 777 + -- 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' +} + +osc777_seq() { + printf '%s' "$1" +} + +print_seq() { + local seq="$1" + + if [ "${OSC777_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}" + ;; + *) + osc777_seq "${seq}" + ;; + esac +} + +notify() { + local title=$1 + local message=$2 + + # shellcheck disable=1003 + print_seq "$(printf '\033]777;notify;%s;%s\e\\' "${title}" "${message}")" +} + +main() { + set -e + + local args=() + while [ $# -gt 0 ]; do + case $1 in + -h | --help) + usage + ;; + -d | --no-dcs) + OSC777_NO_DCS=1 + ;; + --) + shift + args+=("$@") + break + ;; + -*) + usage "Unknown option: $1" + ;; + *) + args+=("$1") + ;; + esac + shift + done + + if [ "${#args[@]}" -ne 2 ]; then + usage "Supply exactly two arguments" + fi + + notify "${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..23363dc --- /dev/null +++ b/pkgs/rbw-pass/rbw-pass @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +usage() { + printf '%s\n' "Usage: rbw-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..11a6c90 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, stevenblack-blocklist }: +stdenvNoCC.mkDerivation { name = "unbound-zones-adblock"; - version = unified-hosts-lists.version; + inherit (stevenblack-blocklist) version; - src = unified-hosts-lists; + src = stevenblack-blocklist; - 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 = "" }'' @@ -18,9 +18,11 @@ stdenvNoCC.mkDerivation rec { ]; in '' - mkdir -p $out - for file in $src/*; do - ${gawkCmd} $file | tr '[:upper:]' '[:lower:]' | sort -u > $out/$(basename $file) + shopt -s globstar + for file in $src/**/hosts; do + outFile="$out/''${file#$src}" + mkdir -p "$(dirname "$outFile")" + ${gawkCmd} $file | tr '[:upper:]' '[:lower:]' | sort -u > "$outFile" done ''; @@ -28,10 +30,11 @@ stdenvNoCC.mkDerivation rec { description = "Unified host lists, ready to be used by unbound"; longDescription = '' This is a simple derivation based on StevenBlack's unified hosts list. - The files have been modified for easy use wih unbound. + The files have been modified for easy use with unbound. ''; 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 deleted file mode 100644 index af55994..0000000 --- a/pkgs/unified-hosts-lists/default.nix +++ /dev/null @@ -1,33 +0,0 @@ -{ fetchFromGitHub, lib, stdenvNoCC }: -stdenvNoCC.mkDerivation rec { - pname = "unified-hosts-lists"; - version = "3.6.4"; - - src = fetchFromGitHub { - owner = "StevenBlack"; - repo = "hosts"; - rev = version; - sha256 = "sha256-U6vRwbFSYka2VS8M1z0n+FaTkKKwdV/cCWIKxp487/I="; - }; - - phases = [ "installPhase" ]; - - installPhase = '' - mkdir -p $out - cp -r $src/hosts $out - for file in $src/alternates/*/hosts; do - cp $file $out/$(basename $(dirname $file)) - done - ''; - - meta = with lib; { - description = "Unified host lists"; - longDescription = '' - Consolidating and extending hosts files from several well-curated sources. - Optionally pick extensions for porn, social media, and other categories. - ''; - homepage = "https://github.com/StevenBlack/hosts"; - license = licenses.mit; - platforms = platforms.all; - }; -} diff --git a/pkgs/zsh-done/default.nix b/pkgs/zsh-done/default.nix new file mode 100644 index 0000000..8fac813 --- /dev/null +++ b/pkgs/zsh-done/default.nix @@ -0,0 +1,34 @@ +{ lib, fetchFromGitHub, stdenvNoCC }: + +stdenvNoCC.mkDerivation rec { + pname = "zsh-done"; + version = "0.1.1"; + + src = fetchFromGitHub { + owner = "ambroisie"; + repo = "zsh-done"; + rev = "v${version}"; + hash = "sha256-dyhPhoMrAfDWtrBX5TA+B3G7QZ7gBhoDGNOEqGsCBQU="; + }; + + dontConfigure = true; + + dontBuild = true; + + installPhase = '' + plugindir="$out/share/zsh/site-functions" + + mkdir -p $plugindir + cp $src/done.plugin.zsh $plugindir/ + ''; + + meta = with lib; { + description = '' + A zsh plug-in to receive notifications when long processes finish + ''; + homepage = "https://git.belanyi.fr/ambroisie/zsh-done"; + license = licenses.mit; + platforms = platforms.unix; + maintainers = with maintainers; [ ambroisie ]; + }; +} 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/matrix.nix b/services/matrix.nix deleted file mode 100644 index 8dc6cad..0000000 --- a/services/matrix.nix +++ /dev/null @@ -1,189 +0,0 @@ -# Matrix homeserver setup, using different endpoints for federation and client -# traffic. The main trick for this is defining two nginx servers endpoints for -# matrix.domain.com, each listening on different ports. -# -# Configuration shamelessly stolen from [1] -# -# [1]: https://github.com/alarsyo/nixos-config/blob/main/services/matrix.nix -{ config, lib, pkgs, ... }: - -with lib; -let - cfg = config.my.services.matrix; - - federationPort = { public = 8448; private = 11338; }; - clientPort = { public = 443; private = 11339; }; - domain = config.networking.domain; -in -{ - options.my.services.matrix = with lib; { - enable = mkEnableOption "Matrix Synapse"; - secret = mkOption { - type = types.str; - example = "deadbeef"; - description = "Shared secret to register users"; - }; - }; - - 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" - TEMPLATE template0 - LC_COLLATE = "C" - LC_CTYPE = "C"; - ''; - }; - - 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; - - listeners = [ - # Federation - { - bind_address = "::1"; - port = federationPort.private; - tls = false; # Terminated by nginx. - x_forwarded = true; - resources = [{ names = [ "federation" ]; compress = false; }]; - } - - # Client - { - bind_address = "::1"; - port = clientPort.private; - tls = false; # Terminated by nginx. - x_forwarded = true; - resources = [{ names = [ "client" ]; compress = false; }]; - } - ]; - }; - - services.nginx.virtualHosts = { - "matrix.${domain}" = { - forceSSL = true; - useACMEHost = domain; - - locations = - let - proxyToClientPort = { - proxyPass = "http://[::1]:${toString clientPort.private}"; - }; - in - { - # Or do a redirect instead of the 404, or whatever is appropriate - # for you. But do not put a Matrix Web client here! See the - # Element web section below. - "/".return = "404"; - - "/_matrix" = proxyToClientPort; - "/_synapse/client" = proxyToClientPort; - }; - - listen = [ - { addr = "0.0.0.0"; port = clientPort.public; ssl = true; } - { addr = "[::]"; port = clientPort.public; ssl = true; } - ]; - - }; - - # same as above, but listening on the federation port - "matrix.${domain}_federation" = rec { - forceSSL = true; - serverName = "matrix.${domain}"; - useACMEHost = domain; - - locations."/".return = "404"; - - locations."/_matrix" = { - proxyPass = "http://[::1]:${toString federationPort.private}"; - }; - - listen = [ - { addr = "0.0.0.0"; port = federationPort.public; ssl = true; } - { addr = "[::]"; port = federationPort.public; ssl = true; } - ]; - - }; - - "${domain}" = { - forceSSL = true; - useACMEHost = domain; - - locations."= /.well-known/matrix/server".extraConfig = - let - server = { "m.server" = "matrix.${domain}:${toString federationPort.public}"; }; - in - '' - add_header Content-Type application/json; - return 200 '${builtins.toJSON server}'; - ''; - - locations."= /.well-known/matrix/client".extraConfig = - let - client = { - "m.homeserver" = { "base_url" = "https://matrix.${domain}"; }; - "m.identity_server" = { "base_url" = "https://vector.im"; }; - }; - # ACAO required to allow element-web on any URL to request this json file - in - '' - add_header Content-Type application/json; - add_header Access-Control-Allow-Origin *; - 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. - environment.systemPackages = [ pkgs.matrix-synapse ]; - - networking.firewall.allowedTCPPorts = [ - clientPort.public - federationPort.public - ]; - - my.services.backup = { - paths = [ - config.services.matrix-synapse.dataDir - ]; - }; - }; -} 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/nextcloud.nix b/services/nextcloud.nix deleted file mode 100644 index d52e32a..0000000 --- a/services/nextcloud.nix +++ /dev/null @@ -1,75 +0,0 @@ -# A self-hosted cloud. -{ 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; { - enable = mkEnableOption "Nextcloud"; - maxSize = mkOption { - type = types.str; - default = "512M"; - example = "1G"; - description = "Maximum file upload size"; - }; - admin = mkOption { - type = types.str; - default = "Ambroisie"; - example = "admin"; - description = "Name of the admin user"; - }; - password = mkOption { - type = types.str; - example = "password"; - description = "The admin user's password"; - }; - }; - - config = lib.mkIf cfg.enable { - services.nextcloud = { - enable = true; - package = pkgs.nextcloud21; - hostName = nextcloudDomain; - home = "/var/lib/nextcloud"; - maxUploadSize = cfg.maxSize; - config = { - adminuser = cfg.admin; - adminpass = cfg.password; # Insecure, but I don't care - dbtype = "pgsql"; - dbhost = "/run/postgresql"; - overwriteProtocol = "https"; # Nginx only allows SSL - }; - }; - - services.postgresql = { - enable = true; - ensureDatabases = [ "nextcloud" ]; - ensureUsers = [ - { - name = "nextcloud"; - ensurePermissions."DATABASE nextcloud" = "ALL PRIVILEGES"; - } - ]; - }; - - systemd.services."nextcloud-setup" = { - requires = [ "postgresql.service" ]; - after = [ "postgresql.service" ]; - }; - - services.nginx.virtualHosts."${nextcloudDomain}" = { - forceSSL = true; - useACMEHost = domain; - - locations."/".proxyPass = "http://127.0.0.1:3000/"; - }; - - my.services.backup = { - paths = [ - config.services.nextcloud.home - ]; - }; - }; -} 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/pirate.nix b/services/pirate.nix deleted file mode 100644 index 2eb490b..0000000 --- a/services/pirate.nix +++ /dev/null @@ -1,43 +0,0 @@ -# The total autonomous media delivery system. -# Relevant link [1]. -# -# [1]: https://youtu.be/I26Ql-uX6AM -{ config, lib, ... }: -let - cfg = config.my.services.pirate; - domain = config.networking.domain; - - ports = { - sonarr = 8989; - radarr = 7878; - bazarr = 6767; - lidarr = 8686; - }; - - managers = with lib.attrsets; - (mapAttrs - (_: _: { - enable = true; - group = "media"; - }) - 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); -in -{ - options.my.services.pirate = { - enable = lib.mkEnableOption "Media automation"; - }; - - config = lib.mkIf cfg.enable { - services = managers // { nginx.virtualHosts = redirections; }; - }; -} 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 100755 index 0000000..390d06d --- /dev/null +++ b/templates/c++-cmake/.envrc @@ -0,0 +1,6 @@ +# shellcheck shell=bash +if ! has nix_direnv_version || ! nix_direnv_version 3.0.0; then + source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.0/direnvrc" "sha256-21TMnI2xWX7HkSTjFFri2UaohXVj854mgvWapWrxRXg=" +fi + +use flake 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..272c0e4 --- /dev/null +++ b/templates/c++-cmake/.woodpecker/check.yml @@ -0,0 +1,31 @@ +labels: + backend: local + +steps: +- name: pre-commit check + image: bash + commands: + - nix develop --command pre-commit run --all + +- name: nix flake check + image: bash + commands: + - nix flake check + +- name: notify + image: bash + environment: + ADDRESS: + from_secret: matrix_homeserver + ROOM: + from_secret: matrix_roomid + USER: + from_secret: matrix_username + PASS: + from_secret: matrix_password + commands: + - nix run github:ambroisie/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..7796f5e --- /dev/null +++ b/templates/c++-cmake/flake.nix @@ -0,0 +1,111 @@ +{ + 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"; + }; + + git-hooks = { + type = "github"; + owner = "cachix"; + repo = "git-hooks.nix"; + ref = "master"; + inputs = { + nixpkgs.follows = "nixpkgs"; + }; + }; + }; + + outputs = { self, futils, nixpkgs, git-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://git.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 = git-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 = [ + self.packages.${system}.project + ]; + + packages = with pkgs; [ + self.checks.${system}.pre-commit.enabledPackages + ]; + + 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..266e3e3 --- /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() 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..390d06d --- /dev/null +++ b/templates/c++-meson/.envrc @@ -0,0 +1,6 @@ +# shellcheck shell=bash +if ! has nix_direnv_version || ! nix_direnv_version 3.0.0; then + source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.0/direnvrc" "sha256-21TMnI2xWX7HkSTjFFri2UaohXVj854mgvWapWrxRXg=" +fi + +use flake 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..272c0e4 --- /dev/null +++ b/templates/c++-meson/.woodpecker/check.yml @@ -0,0 +1,31 @@ +labels: + backend: local + +steps: +- name: pre-commit check + image: bash + commands: + - nix develop --command pre-commit run --all + +- name: nix flake check + image: bash + commands: + - nix flake check + +- name: notify + image: bash + environment: + ADDRESS: + from_secret: matrix_homeserver + ROOM: + from_secret: matrix_roomid + USER: + from_secret: matrix_username + PASS: + from_secret: matrix_password + commands: + - nix run github:ambroisie/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..cb14eb5 --- /dev/null +++ b/templates/c++-meson/flake.nix @@ -0,0 +1,111 @@ +{ + 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"; + }; + + git-hooks = { + type = "github"; + owner = "cachix"; + repo = "git-hooks.nix"; + ref = "master"; + inputs = { + nixpkgs.follows = "nixpkgs"; + }; + }; + }; + + outputs = { self, futils, nixpkgs, git-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://git.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 = git-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 = [ + self.packages.${system}.project + ]; + + packages = with pkgs; [ + self.checks.${system}.pre-commit.enabledPackages + ]; + + 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..51864cd --- /dev/null +++ b/templates/default.nix @@ -0,0 +1,18 @@ +{ + "c++-cmake" = { + path = ./c++-cmake; + description = "A C++ project using CMake"; + }; + "c++-meson" = { + path = ./c++-meson; + description = "A C++ project using Meson"; + }; + "python-uv" = { + path = ./python-uv; + description = "A Python project using uv"; + }; + "rust-cargo" = { + path = ./rust-cargo; + description = "A Rust project using Cargo"; + }; +} diff --git a/templates/python-uv/.envrc b/templates/python-uv/.envrc new file mode 100644 index 0000000..390d06d --- /dev/null +++ b/templates/python-uv/.envrc @@ -0,0 +1,6 @@ +# shellcheck shell=bash +if ! has nix_direnv_version || ! nix_direnv_version 3.0.0; then + source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.0/direnvrc" "sha256-21TMnI2xWX7HkSTjFFri2UaohXVj854mgvWapWrxRXg=" +fi + +use flake diff --git a/templates/python-uv/.gitignore b/templates/python-uv/.gitignore new file mode 100644 index 0000000..c79d1e8 --- /dev/null +++ b/templates/python-uv/.gitignore @@ -0,0 +1,6 @@ +# Virtual environments +.venv + +# Nix generated files +/.pre-commit-config.yaml +/result diff --git a/templates/python-uv/.woodpecker/check.yml b/templates/python-uv/.woodpecker/check.yml new file mode 100644 index 0000000..272c0e4 --- /dev/null +++ b/templates/python-uv/.woodpecker/check.yml @@ -0,0 +1,31 @@ +labels: + backend: local + +steps: +- name: pre-commit check + image: bash + commands: + - nix develop --command pre-commit run --all + +- name: nix flake check + image: bash + commands: + - nix flake check + +- name: notify + image: bash + environment: + ADDRESS: + from_secret: matrix_homeserver + ROOM: + from_secret: matrix_roomid + USER: + from_secret: matrix_username + PASS: + from_secret: matrix_password + commands: + - nix run github:ambroisie/matrix-notifier + when: + status: + - failure + - success diff --git a/templates/python-uv/flake.nix b/templates/python-uv/flake.nix new file mode 100644 index 0000000..5059e64 --- /dev/null +++ b/templates/python-uv/flake.nix @@ -0,0 +1,112 @@ +{ + description = "A Python project"; + + inputs = { + futils = { + type = "github"; + owner = "numtide"; + repo = "flake-utils"; + ref = "main"; + }; + + nixpkgs = { + type = "github"; + owner = "NixOS"; + repo = "nixpkgs"; + ref = "nixos-unstable"; + }; + + git-hooks = { + type = "github"; + owner = "cachix"; + repo = "git-hooks.nix"; + ref = "master"; + inputs = { + nixpkgs.follows = "nixpkgs"; + }; + }; + }; + + outputs = { self, futils, nixpkgs, git-hooks }: + { + overlays = { + default = final: _prev: { + project = with final; python3.pkgs.buildPythonApplication { + pname = "project"; + version = (final.lib.importTOML ./pyproject.toml).project.version; + pyproject = true; + + src = self; + + build-system = with python3.pkgs; [ setuptools ]; + + pythonImportsCheck = [ "project" ]; + + meta = with lib; { + description = "A Python project"; + homepage = "https://git.belanyi.fr/ambroisie/project"; + license = licenses.mit; + maintainers = with maintainers; [ ambroisie ]; + }; + }; + }; + }; + } // futils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { + inherit system; + overlays = [ + self.overlays.default + ]; + }; + + pre-commit = git-hooks.lib.${system}.run { + src = self; + + hooks = { + mypy = { + enable = true; + }; + + nixpkgs-fmt = { + enable = true; + }; + + ruff = { + enable = true; + }; + + ruff-format = { + enable = true; + }; + }; + }; + in + { + checks = { + inherit (self.packages.${system}) project; + + inherit pre-commit; + }; + + devShells = { + default = pkgs.mkShell { + inputsFrom = [ + self.packages.${system}.project + ]; + + packages = with pkgs; [ + uv + self.checks.${system}.pre-commit.enabledPackages + ]; + + inherit (pre-commit) shellHook; + }; + }; + + packages = futils.lib.flattenTree { + default = pkgs.project; + inherit (pkgs) project; + }; + }); +} diff --git a/templates/python-uv/pyproject.toml b/templates/python-uv/pyproject.toml new file mode 100644 index 0000000..7b2d896 --- /dev/null +++ b/templates/python-uv/pyproject.toml @@ -0,0 +1,17 @@ +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + + +[project] +name = "project" +version = "0.0.0" +description = "project description" +requires-python = ">=3.12" +dependencies = [] + +[project.scripts] +project = "project:main" + +[dependency-groups] +dev = [] diff --git a/templates/python-uv/src/project/__init__.py b/templates/python-uv/src/project/__init__.py new file mode 100644 index 0000000..b06117d --- /dev/null +++ b/templates/python-uv/src/project/__init__.py @@ -0,0 +1,2 @@ +def main() -> None: + print("Hello, world!") diff --git a/templates/rust-cargo/.envrc b/templates/rust-cargo/.envrc new file mode 100644 index 0000000..390d06d --- /dev/null +++ b/templates/rust-cargo/.envrc @@ -0,0 +1,6 @@ +# shellcheck shell=bash +if ! has nix_direnv_version || ! nix_direnv_version 3.0.0; then + source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.0/direnvrc" "sha256-21TMnI2xWX7HkSTjFFri2UaohXVj854mgvWapWrxRXg=" +fi + +use flake diff --git a/templates/rust-cargo/.gitignore b/templates/rust-cargo/.gitignore new file mode 100644 index 0000000..5f360ff --- /dev/null +++ b/templates/rust-cargo/.gitignore @@ -0,0 +1,6 @@ +# Rust build directory +/target + +# Nix generated files +/.pre-commit-config.yaml +/result diff --git a/templates/rust-cargo/.woodpecker/check.yml b/templates/rust-cargo/.woodpecker/check.yml new file mode 100644 index 0000000..272c0e4 --- /dev/null +++ b/templates/rust-cargo/.woodpecker/check.yml @@ -0,0 +1,31 @@ +labels: + backend: local + +steps: +- name: pre-commit check + image: bash + commands: + - nix develop --command pre-commit run --all + +- name: nix flake check + image: bash + commands: + - nix flake check + +- name: notify + image: bash + environment: + ADDRESS: + from_secret: matrix_homeserver + ROOM: + from_secret: matrix_roomid + USER: + from_secret: matrix_username + PASS: + from_secret: matrix_password + commands: + - nix run github:ambroisie/matrix-notifier + when: + status: + - failure + - success diff --git a/templates/rust-cargo/Cargo.lock b/templates/rust-cargo/Cargo.lock new file mode 100644 index 0000000..4f9c86e --- /dev/null +++ b/templates/rust-cargo/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "project" +version = "0.0.0" diff --git a/templates/rust-cargo/Cargo.toml b/templates/rust-cargo/Cargo.toml new file mode 100644 index 0000000..4dfdc0b --- /dev/null +++ b/templates/rust-cargo/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "project" +version = "0.0.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/templates/rust-cargo/flake.nix b/templates/rust-cargo/flake.nix new file mode 100644 index 0000000..efd8358 --- /dev/null +++ b/templates/rust-cargo/flake.nix @@ -0,0 +1,110 @@ +{ + description = "A Rust project"; + + inputs = { + futils = { + type = "github"; + owner = "numtide"; + repo = "flake-utils"; + ref = "main"; + }; + + nixpkgs = { + type = "github"; + owner = "NixOS"; + repo = "nixpkgs"; + ref = "nixos-unstable"; + }; + + git-hooks = { + type = "github"; + owner = "cachix"; + repo = "git-hooks.nix"; + ref = "master"; + inputs = { + nixpkgs.follows = "nixpkgs"; + }; + }; + }; + + outputs = { self, futils, nixpkgs, git-hooks }: + { + overlays = { + default = final: _prev: { + project = with final; rustPlatform.buildRustPackage { + pname = "project"; + version = (final.lib.importTOML ./Cargo.toml).package.version; + + src = self; + + cargoLock = { + lockFile = "${self}/Cargo.lock"; + }; + + meta = with lib; { + description = "A Rust project"; + homepage = "https://git.belanyi.fr/ambroisie/project"; + license = licenses.mit; + maintainers = with maintainers; [ ambroisie ]; + }; + }; + }; + }; + } // futils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { + inherit system; + overlays = [ + self.overlays.default + ]; + }; + + pre-commit = git-hooks.lib.${system}.run { + src = self; + + hooks = { + clippy = { + enable = true; + settings = { + denyWarnings = true; + }; + }; + + nixpkgs-fmt = { + enable = true; + }; + + rustfmt = { + enable = true; + }; + }; + }; + in + { + checks = { + inherit (self.packages.${system}) project; + }; + + devShells = { + default = pkgs.mkShell { + inputsFrom = [ + self.packages.${system}.project + ]; + + packages = with pkgs; [ + rust-analyzer + self.checks.${system}.pre-commit.enabledPackages + ]; + + RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}"; + + inherit (pre-commit) shellHook; + }; + }; + + packages = futils.lib.flattenTree { + default = pkgs.project; + inherit (pkgs) project; + }; + }); +} diff --git a/templates/rust-cargo/rustfmt.toml b/templates/rust-cargo/rustfmt.toml new file mode 100644 index 0000000..e69de29 diff --git a/templates/rust-cargo/src/main.rs b/templates/rust-cargo/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/templates/rust-cargo/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +}