diff --git a/.drone.yml b/.drone.yml
deleted file mode 100644
index 7ad1c78..0000000
--- a/.drone.yml
+++ /dev/null
@@ -1,24 +0,0 @@
-kind: pipeline
-name: check config
-
-steps:
- - name: format check
- image: nixos/nix
- commands:
- - nix-shell -p nixpkgs-fmt --run 'nixpkgs-fmt . --check'
-
- - name: notify
- image: plugins/matrix
- settings:
- homeserver:
- from_secret: matrix_homeserver
- roomid:
- from_secret: matrix_roomid
- username:
- from_secret: matrix_username
- password:
- from_secret: matrix_password
- trigger:
- status:
- - failure
- - success
diff --git a/.envrc b/.envrc
index 9556665..95ed6fb 100644
--- a/.envrc
+++ b/.envrc
@@ -1,8 +1,10 @@
-use_flake() {
- watch_file flake.nix
- watch_file flake.lock
- eval "$(nix print-dev-env)"
-}
+if ! has nix_direnv_version || ! nix_direnv_version 2.2.1; then
+ source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.2.1/direnvrc" "sha256-zelF0vLbEl5uaqrfIzbgNzJWGmLzCmYAkInj/LNxvKs="
+fi
-ulimit -s unlimited # Bypass current bug in `nix` flakes evaluation
use flake
+
+watch_file ./flake/checks.nix
+watch_file ./flake/dev-shells.nix
+
+eval "$shellHooks"
diff --git a/.git-crypt/.gitattributes b/.git-crypt/.gitattributes
deleted file mode 100644
index 665b10e..0000000
--- a/.git-crypt/.gitattributes
+++ /dev/null
@@ -1,4 +0,0 @@
-# Do not edit this file. To specify the files to encrypt, create your own
-# .gitattributes file in the directory where your files are.
-* !filter !diff
-*.gpg binary
diff --git a/.git-crypt/keys/default/0/06B6C818917564FD1014DD342E4F270130BBF854.gpg b/.git-crypt/keys/default/0/06B6C818917564FD1014DD342E4F270130BBF854.gpg
deleted file mode 100644
index c941bd5..0000000
Binary files a/.git-crypt/keys/default/0/06B6C818917564FD1014DD342E4F270130BBF854.gpg and /dev/null differ
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..fcf7246
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/.pre-commit-config.yaml
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
deleted file mode 100644
index 0543253..0000000
--- a/.pre-commit-config.yaml
+++ /dev/null
@@ -1,21 +0,0 @@
-repos:
-- repo: 'https://github.com/pre-commit/pre-commit-hooks'
- rev: 'v2.3.0'
- hooks:
- - id: 'trailing-whitespace'
- - id: 'end-of-file-fixer'
- - id: 'check-yaml'
- - id: 'check-added-large-files'
-- repo: 'https://github.com/jumanjihouse/pre-commit-hooks'
- rev: '2.1.4'
- hooks:
- - id: 'forbid-binary'
-- repo: 'local'
- hooks:
- - id: 'nixpkgs-fmt'
- name: 'nixpkgs-fmt'
- description: 'Format nix code with nixpkgs-fmt'
- entry: 'nixpkgs-fmt'
- language: 'system'
- files: '\.nix$'
- always_run: true
diff --git a/.stylua.toml b/.stylua.toml
new file mode 100644
index 0000000..394e884
--- /dev/null
+++ b/.stylua.toml
@@ -0,0 +1 @@
+indent_type = "Spaces"
diff --git a/.woodpecker/check.yml b/.woodpecker/check.yml
new file mode 100644
index 0000000..d7c5dff
--- /dev/null
+++ b/.woodpecker/check.yml
@@ -0,0 +1,26 @@
+labels:
+ backend: local
+
+pipeline:
+- name: nix flake check
+ image: bash
+ commands:
+ - nix flake check
+
+- name: notifiy
+ image: bash
+ secrets:
+ - source: matrix_homeserver
+ target: address
+ - source: matrix_roomid
+ target: room
+ - source: matrix_username
+ target: user
+ - source: matrix_password
+ target: pass
+ commands:
+ - nix run '.#matrix-notifier'
+ when:
+ status:
+ - failure
+ - success
diff --git a/README.md b/README.md
index 8d84109..3aa6453 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,8 @@ Secondly, take care of a few manual steps:
* Configure Gitea and Drone
* Configure Lohr webhook and SSH key
* Configure Jellyfin
-* Configure Jackett and NZBHydra2
+* Configure Prowlarr,Jackett and NZBHydra2
* Configure Sonarr, Radarr, Bazarr
* Configure Transmission's webui port
* Configure Quassel user
+* Configure Flood account
diff --git a/bootstrap.sh b/bootstrap.sh
index a3f9ac9..b1c418e 100755
--- a/bootstrap.sh
+++ b/bootstrap.sh
@@ -1,5 +1,5 @@
#!/usr/bin/env nix-shell
-#! nix-shell -i bash -p bitwarden-cli git gnupg jq nixFlakes
+#! nix-shell -i bash -p bitwarden-cli git gnupg jq nix
# Command failure is script failure
set -e
@@ -58,6 +58,8 @@ get_ssh() {
get_doc "SysAdmin/SSH" "shared-key-public" "$HOME/.ssh/shared_rsa.pub" 644
get_doc "SysAdmin/SSH" "shared-key-private" "$HOME/.ssh/shared_rsa" 600
+ get_doc "SysAdmin/SSH" "agenix-public" "$HOME/.ssh/agenix.pub" 644
+ get_doc "SysAdmin/SSH" "agenix-private" "$HOME/.ssh/agenix" 600
}
get_pgp() {
@@ -78,22 +80,13 @@ get_pgp() {
}
get_creds() {
- BW_SESSION="$(bw login --raw)"
+ BW_SESSION="$(bw login --raw || bw unlock --raw)"
export BW_SESSION
get_ssh
get_pgp
}
-setup_gpg() {
- info 'Setting up loopback pinentry for GnuPG'
- echo "allow-loopback-pinentry" > ~/.gnupg/gpg-agent.conf
-
- info 'Signing dummy message to ensure GnuPG key is usable by `git-crypt`'
- echo whatever | gpg --clearsign --armor --pinentry loopback --output /dev/null
-}
-
[ -z "$NOCREDS" ] && get_creds
-[ -z "$NOGPG" ] && setup_gpg
nix --experimental-features 'nix-command flakes' develop
diff --git a/flake.lock b/flake.lock
index f80cf84..b3c3e01 100644
--- a/flake.lock
+++ b/flake.lock
@@ -1,21 +1,129 @@
{
"nodes": {
- "futils": {
+ "agenix": {
+ "inputs": {
+ "darwin": "darwin",
+ "home-manager": [
+ "home-manager"
+ ],
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
"locked": {
- "lastModified": 1619345332,
- "narHash": "sha256-qHnQkEp1uklKTpx3MvKtY6xzgcqXDsz5nLilbbuL+3A=",
+ "lastModified": 1690228878,
+ "narHash": "sha256-9Xe7JV0krp4RJC9W9W9WutZVlw6BlHTFMiUP/k48LQY=",
+ "owner": "ryantm",
+ "repo": "agenix",
+ "rev": "d8c973fd228949736dedf61b7f8cc1ece3236792",
+ "type": "github"
+ },
+ "original": {
+ "owner": "ryantm",
+ "ref": "main",
+ "repo": "agenix",
+ "type": "github"
+ }
+ },
+ "darwin": {
+ "inputs": {
+ "nixpkgs": [
+ "agenix",
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1673295039,
+ "narHash": "sha256-AsdYgE8/GPwcelGgrntlijMg4t3hLFJFCRF3tL5WVjA=",
+ "owner": "lnl7",
+ "repo": "nix-darwin",
+ "rev": "87b9d090ad39b25b2400029c64825fc2a8868943",
+ "type": "github"
+ },
+ "original": {
+ "owner": "lnl7",
+ "ref": "master",
+ "repo": "nix-darwin",
+ "type": "github"
+ }
+ },
+ "flake-compat": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1673956053,
+ "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
+ "type": "github"
+ },
+ "original": {
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "type": "github"
+ }
+ },
+ "flake-parts": {
+ "inputs": {
+ "nixpkgs-lib": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1690933134,
+ "narHash": "sha256-ab989mN63fQZBFrkk4Q8bYxQCktuHmBIBqUG1jl6/FQ=",
+ "owner": "hercules-ci",
+ "repo": "flake-parts",
+ "rev": "59cf3f1447cfc75087e7273b04b31e689a8599fb",
+ "type": "github"
+ },
+ "original": {
+ "owner": "hercules-ci",
+ "ref": "main",
+ "repo": "flake-parts",
+ "type": "github"
+ }
+ },
+ "futils": {
+ "inputs": {
+ "systems": "systems"
+ },
+ "locked": {
+ "lastModified": 1689068808,
+ "narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
"owner": "numtide",
"repo": "flake-utils",
- "rev": "2ebf2558e5bf978c7fb8ea927dfaed8fefab2e28",
+ "rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
"type": "github"
},
"original": {
"owner": "numtide",
- "ref": "master",
+ "ref": "main",
"repo": "flake-utils",
"type": "github"
}
},
+ "gitignore": {
+ "inputs": {
+ "nixpkgs": [
+ "pre-commit-hooks",
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1660459072,
+ "narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=",
+ "owner": "hercules-ci",
+ "repo": "gitignore.nix",
+ "rev": "a20de23b925fd8264fd7fad6454652e142fd7f73",
+ "type": "github"
+ },
+ "original": {
+ "owner": "hercules-ci",
+ "repo": "gitignore.nix",
+ "type": "github"
+ }
+ },
"home-manager": {
"inputs": {
"nixpkgs": [
@@ -23,11 +131,11 @@
]
},
"locked": {
- "lastModified": 1619558193,
- "narHash": "sha256-DljP5/9EX0eXEPhzCUFqFEHkkcFuXJBx1PTgcv0OgyM=",
+ "lastModified": 1691039228,
+ "narHash": "sha256-iPNZJ1LvfUf1Y456ewC0DXgf99TNssG8OLObOyqxO6M=",
"owner": "nix-community",
"repo": "home-manager",
- "rev": "18ad12d52b8cebbb57013865eec2be5125de050a",
+ "rev": "86dd48d70a2e2c17e84e747ba4faa92453e68d4a",
"type": "github"
},
"original": {
@@ -39,11 +147,11 @@
},
"nixpkgs": {
"locked": {
- "lastModified": 1619464443,
- "narHash": "sha256-R7WAb8EnkIJxxaF6GTHUPytjonhB4Zm0iatyWoW169A=",
+ "lastModified": 1691006197,
+ "narHash": "sha256-DbtxVWPt+ZP5W0Usg7jAyTomIM//c3Jtfa59Ht7AV8s=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "8e4fe32876ca15e3d5eb3ecd3ca0b224417f5f17",
+ "rev": "66aedfd010204949cb225cf749be08cb13ce1813",
"type": "github"
},
"original": {
@@ -55,11 +163,11 @@
},
"nur": {
"locked": {
- "lastModified": 1619628114,
- "narHash": "sha256-s3pQyvMfXVmbQOX224yOWQf6zi8406sShFF4u17LVQ0=",
+ "lastModified": 1691139289,
+ "narHash": "sha256-cZtqvYztpGwLtAsfrzY2VeTfFdW3HBwX7m1KV2Zy2nw=",
"owner": "nix-community",
"repo": "NUR",
- "rev": "0615e756dc14986c4968fa478c0bd080d621cb2b",
+ "rev": "cb20b89d5b355c53a18dd149e7104a67381c7c17",
"type": "github"
},
"original": {
@@ -69,12 +177,59 @@
"type": "github"
}
},
+ "pre-commit-hooks": {
+ "inputs": {
+ "flake-compat": "flake-compat",
+ "flake-utils": [
+ "futils"
+ ],
+ "gitignore": "gitignore",
+ "nixpkgs": [
+ "nixpkgs"
+ ],
+ "nixpkgs-stable": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1691093055,
+ "narHash": "sha256-sjNWYpDHc6vx+/M0WbBZKltR0Avh2S43UiDbmYtfHt0=",
+ "owner": "cachix",
+ "repo": "pre-commit-hooks.nix",
+ "rev": "ebb43bdacd1af8954d04869c77bc3b61fde515e4",
+ "type": "github"
+ },
+ "original": {
+ "owner": "cachix",
+ "ref": "master",
+ "repo": "pre-commit-hooks.nix",
+ "type": "github"
+ }
+ },
"root": {
"inputs": {
+ "agenix": "agenix",
+ "flake-parts": "flake-parts",
"futils": "futils",
"home-manager": "home-manager",
"nixpkgs": "nixpkgs",
- "nur": "nur"
+ "nur": "nur",
+ "pre-commit-hooks": "pre-commit-hooks"
+ }
+ },
+ "systems": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
}
}
},
diff --git a/flake.nix b/flake.nix
index f582719..8e46ea3 100644
--- a/flake.nix
+++ b/flake.nix
@@ -1,11 +1,32 @@
{
description = "NixOS configuration with flakes";
inputs = {
+ agenix = {
+ type = "github";
+ owner = "ryantm";
+ repo = "agenix";
+ ref = "main";
+ inputs = {
+ home-manager.follows = "home-manager";
+ nixpkgs.follows = "nixpkgs";
+ };
+ };
+
+ flake-parts = {
+ type = "github";
+ owner = "hercules-ci";
+ repo = "flake-parts";
+ ref = "main";
+ inputs = {
+ nixpkgs-lib.follows = "nixpkgs";
+ };
+ };
+
futils = {
type = "github";
owner = "numtide";
repo = "flake-utils";
- ref = "master";
+ ref = "main";
};
home-manager = {
@@ -31,79 +52,20 @@
repo = "NUR";
ref = "master";
};
- };
- outputs = { self, futils, home-manager, nixpkgs, nur } @ inputs:
- let
- inherit (futils.lib) eachDefaultSystem;
-
- lib = nixpkgs.lib.extend (self: super: {
- my = import ./lib { inherit inputs; pkgs = nixpkgs; lib = self; };
- });
-
- defaultModules = [
- ({ ... }: {
- # Let 'nixos-version --json' know about the Git revision
- system.configurationRevision = self.rev or "dirty";
- })
- {
- nixpkgs.overlays = (lib.attrValues self.overlays) ++ [
- nur.overlay
- ];
- }
- home-manager.nixosModules.home-manager
- {
- home-manager.users.ambroisie = import ./home;
- # Nix Flakes compatibility
- home-manager.useGlobalPkgs = true;
- home-manager.useUserPackages = true;
- }
- ];
-
- buildHost = name: system: lib.nixosSystem {
- inherit system;
- modules = defaultModules ++ [
- (./. + "/${name}.nix")
- ];
- specialArgs = {
- # Use my extended lib in NixOS configuration
- inherit lib;
- };
- };
- in
- eachDefaultSystem
- (system:
- let
- pkgs = nixpkgs.legacyPackages.${system};
- in
- rec {
- apps = {
- diff-flake = futils.lib.mkApp { drv = packages.diff-flake; };
- };
-
- defaultApp = apps.diff-flake;
-
- devShell = pkgs.mkShell {
- name = "NixOS-config";
- buildInputs = with pkgs; [
- git-crypt
- gitAndTools.pre-commit
- gnupg
- nixpkgs-fmt
- ];
- };
-
- packages = import ./pkgs { inherit pkgs; };
- }) // {
- overlay = self.overlays.pkgs;
-
- overlays = {
- lib = final: prev: { inherit lib; };
- pkgs = final: prev: { ambroisie = import ./pkgs { pkgs = prev; }; };
- };
-
- nixosConfigurations = lib.mapAttrs buildHost {
- porthos = "x86_64-linux";
+ pre-commit-hooks = {
+ type = "github";
+ owner = "cachix";
+ repo = "pre-commit-hooks.nix";
+ ref = "master";
+ inputs = {
+ flake-utils.follows = "futils";
+ nixpkgs.follows = "nixpkgs";
+ nixpkgs-stable.follows = "nixpkgs";
};
};
+ };
+
+ # Can't eta-reduce a flake outputs...
+ outputs = inputs: import ./flake inputs;
}
diff --git a/flake/apps.nix b/flake/apps.nix
new file mode 100644
index 0000000..f8dc2de
--- /dev/null
+++ b/flake/apps.nix
@@ -0,0 +1,9 @@
+{ inputs, ... }:
+{
+ perSystem = { self', ... }: {
+ apps = {
+ diff-flake = inputs.futils.lib.mkApp { drv = self'.packages.diff-flake; };
+ default = self'.apps.diff-flake;
+ };
+ };
+}
diff --git a/flake/checks.nix b/flake/checks.nix
new file mode 100644
index 0000000..98e49bd
--- /dev/null
+++ b/flake/checks.nix
@@ -0,0 +1,33 @@
+{ inputs, ... }:
+{
+ imports = [
+ inputs.pre-commit-hooks.flakeModule
+ ];
+
+ perSystem = { ... }: {
+ pre-commit = {
+ # Add itself to `nix flake check`
+ check.enable = true;
+
+ settings = {
+ hooks = {
+ deadnix = {
+ enable = true;
+ };
+
+ nixpkgs-fmt = {
+ enable = true;
+ };
+
+ shellcheck = {
+ enable = true;
+ };
+
+ stylua = {
+ enable = true;
+ };
+ };
+ };
+ };
+ };
+}
diff --git a/flake/default.nix b/flake/default.nix
new file mode 100644
index 0000000..65102e1
--- /dev/null
+++ b/flake/default.nix
@@ -0,0 +1,22 @@
+{ flake-parts
+, futils
+, ...
+} @ inputs:
+let
+ mySystems = futils.lib.defaultSystems;
+in
+flake-parts.lib.mkFlake { inherit inputs; } {
+ systems = mySystems;
+
+ imports = [
+ ./apps.nix
+ ./checks.nix
+ ./dev-shells.nix
+ ./home-manager.nix
+ ./lib.nix
+ ./nixos.nix
+ ./overlays.nix
+ ./packages.nix
+ ./templates.nix
+ ];
+}
diff --git a/flake/dev-shells.nix b/flake/dev-shells.nix
new file mode 100644
index 0000000..d5f5989
--- /dev/null
+++ b/flake/dev-shells.nix
@@ -0,0 +1,19 @@
+{ ... }:
+{
+ perSystem = { config, pkgs, ... }: {
+ devShells = {
+ default = pkgs.mkShellNoCC {
+ name = "NixOS-config";
+
+ nativeBuildInputs = with pkgs; [
+ gitAndTools.pre-commit
+ nixpkgs-fmt
+ ];
+
+ shellHook = ''
+ ${config.pre-commit.installationScript}
+ '';
+ };
+ };
+ };
+}
diff --git a/flake/home-manager.nix b/flake/home-manager.nix
new file mode 100644
index 0000000..c55c8dd
--- /dev/null
+++ b/flake/home-manager.nix
@@ -0,0 +1,61 @@
+{ self, inputs, lib, ... }:
+let
+ defaultModules = [
+ # Include generic settings
+ "${self}/home"
+ {
+ # Basic user information defaults
+ home.username = lib.mkDefault "ambroisie";
+ home.homeDirectory = lib.mkDefault "/home/ambroisie";
+
+ # Make it a Linux installation by default
+ targets.genericLinux.enable = lib.mkDefault true;
+
+ # Enable home-manager
+ programs.home-manager.enable = true;
+ }
+ ];
+
+ mkHome = name: system: inputs.home-manager.lib.homeManagerConfiguration {
+ # Work-around for home-manager
+ # * not letting me set `lib` as an extraSpecialArgs
+ # * not respecting `nixpkgs.overlays` [1]
+ # [1]: https://github.com/nix-community/home-manager/issues/2954
+ pkgs = import inputs.nixpkgs {
+ inherit system;
+
+ overlays = (lib.attrValues self.overlays) ++ [
+ inputs.nur.overlay
+ ];
+ };
+
+ modules = defaultModules ++ [
+ "${self}/hosts/homes/${name}"
+ ];
+
+ extraSpecialArgs = {
+ # Inject inputs to use them in global registry
+ inherit inputs;
+ };
+ };
+
+ hosts = {
+ "ambroisie@ambroisie" = "x86_64-linux"; # Unfortunate naming here...
+ };
+in
+{
+ perSystem = { system, ... }: {
+ # Work-around for https://github.com/nix-community/home-manager/issues/3075
+ legacyPackages = {
+ homeConfigurations =
+ let
+ filteredHosts = lib.filterAttrs (_: v: v == system) hosts;
+ allHosts = filteredHosts // {
+ # Default configuration
+ ambroisie = system;
+ };
+ in
+ lib.mapAttrs mkHome allHosts;
+ };
+ };
+}
diff --git a/flake/lib.nix b/flake/lib.nix
new file mode 100644
index 0000000..12e89c3
--- /dev/null
+++ b/flake/lib.nix
@@ -0,0 +1,11 @@
+{ self, inputs, ... }:
+let
+ inherit (inputs) nixpkgs;
+
+ lib = nixpkgs.lib.extend (final: _: {
+ my = import "${self}/lib" { inherit inputs; pkgs = nixpkgs; lib = final; };
+ });
+in
+{
+ flake.lib = lib;
+}
diff --git a/flake/nixos.nix b/flake/nixos.nix
new file mode 100644
index 0000000..9eb6388
--- /dev/null
+++ b/flake/nixos.nix
@@ -0,0 +1,39 @@
+{ self, inputs, ... }:
+let
+ inherit (self) lib;
+
+ defaultModules = [
+ {
+ # Let 'nixos-version --json' know about the Git revision
+ system.configurationRevision = self.rev or "dirty";
+ }
+ {
+ nixpkgs.overlays = (lib.attrValues self.overlays) ++ [
+ inputs.nur.overlay
+ ];
+ }
+ # Include generic settings
+ "${self}/modules"
+ # Include bundles of settings
+ "${self}/profiles"
+ ];
+
+ buildHost = name: system: lib.nixosSystem {
+ inherit system;
+ modules = defaultModules ++ [
+ "${self}/hosts/nixos/${name}"
+ ];
+ specialArgs = {
+ # Use my extended lib in NixOS configuration
+ inherit lib;
+ # Inject inputs to use them in global registry
+ inherit inputs;
+ };
+ };
+in
+{
+ flake.nixosConfigurations = lib.mapAttrs buildHost {
+ aramis = "x86_64-linux";
+ porthos = "x86_64-linux";
+ };
+}
diff --git a/flake/overlays.nix b/flake/overlays.nix
new file mode 100644
index 0000000..0c47989
--- /dev/null
+++ b/flake/overlays.nix
@@ -0,0 +1,17 @@
+{ self, ... }:
+let
+ default-overlays = import "${self}/overlays";
+
+ additional-overlays = {
+ # Expose my expanded library
+ lib = _final: _prev: { inherit (self) lib; };
+
+ # Expose my custom packages
+ pkgs = _final: prev: {
+ ambroisie = prev.recurseIntoAttrs (import "${self}/pkgs" { pkgs = prev; });
+ };
+ };
+in
+{
+ flake.overlays = default-overlays // additional-overlays;
+}
diff --git a/flake/packages.nix b/flake/packages.nix
new file mode 100644
index 0000000..3515071
--- /dev/null
+++ b/flake/packages.nix
@@ -0,0 +1,13 @@
+{ self, inputs, ... }:
+{
+ perSystem = { pkgs, system, ... }: {
+ packages =
+ let
+ inherit (inputs.futils.lib) filterPackages flattenTree;
+ packages = import "${self}/pkgs" { inherit pkgs; };
+ flattenedPackages = flattenTree packages;
+ finalPackages = filterPackages system flattenedPackages;
+ in
+ finalPackages;
+ };
+}
diff --git a/flake/templates.nix b/flake/templates.nix
new file mode 100644
index 0000000..28e534e
--- /dev/null
+++ b/flake/templates.nix
@@ -0,0 +1,4 @@
+{ self, ... }:
+{
+ flake.templates = import "${self}/templates";
+}
diff --git a/home/aliases/default.nix b/home/aliases/default.nix
new file mode 100644
index 0000000..259f148
--- /dev/null
+++ b/home/aliases/default.nix
@@ -0,0 +1,26 @@
+{ config, lib, ... }:
+let
+ cfg = config.my.home.aliases;
+in
+{
+ options.my.home.aliases = with lib; {
+ enable = my.mkDisableOption "shell aliases configuration";
+ };
+
+ config = lib.mkIf cfg.enable {
+ home = {
+ shellAliases = {
+ # I like pretty colors
+ diff = "diff --color=auto";
+ grep = "grep --color=auto";
+ egrep = "egrep --color=auto";
+ fgrep = "fgrep --color=auto";
+ ls = "ls --color=auto";
+
+ # Well-known ls aliases
+ l = "ls -alh";
+ ll = "ls -l";
+ };
+ };
+ };
+}
diff --git a/home/atuin/default.nix b/home/atuin/default.nix
new file mode 100644
index 0000000..19a6fb9
--- /dev/null
+++ b/home/atuin/default.nix
@@ -0,0 +1,31 @@
+{ config, lib, ... }:
+let
+ cfg = config.my.home.atuin;
+in
+{
+ options.my.home.atuin = with lib; {
+ enable = my.mkDisableOption "atuin configuration";
+ };
+
+ config = lib.mkIf cfg.enable {
+ programs.atuin = {
+ enable = true;
+
+ flags = [
+ # I *despise* this hijacking of the up key, even though I use Ctrl-p
+ "--disable-up-arrow"
+ ];
+
+ settings = {
+ # The package is managed by Nix
+ update_check = false;
+ # I don't care for the fancy display
+ style = "compact";
+ # Get closer to fzf's fuzzy search
+ search_mode = "skim";
+ # Show long command lines at the bottom
+ show_preview = true;
+ };
+ };
+ };
+}
diff --git a/home/bat.nix b/home/bat/default.nix
similarity index 65%
rename from home/bat.nix
rename to home/bat/default.nix
index 8485dd3..67d80c2 100644
--- a/home/bat.nix
+++ b/home/bat/default.nix
@@ -3,13 +3,15 @@ let
cfg = config.my.home.bat;
in
{
- options.my.home.bat = with lib.my; {
- enable = mkDisableOption "bat configuration";
+ options.my.home.bat = with lib; {
+ enable = my.mkDisableOption "bat configuration";
};
config.programs.bat = lib.mkIf cfg.enable {
enable = true;
config = {
+ theme = "gruvbox-dark";
+
pager = with config.home.sessionVariables; "${PAGER} ${LESS}";
};
};
diff --git a/home/bitwarden/default.nix b/home/bitwarden/default.nix
new file mode 100644
index 0000000..c709f7b
--- /dev/null
+++ b/home/bitwarden/default.nix
@@ -0,0 +1,27 @@
+{ config, lib, ... }:
+let
+ cfg = config.my.home.bitwarden;
+in
+{
+ options.my.home.bitwarden = with lib; {
+ enable = my.mkDisableOption "bitwarden configuration";
+
+ pinentry = mkOption {
+ type = types.str;
+ default = "tty";
+ example = "gtk2";
+ description = "Which pinentry interface to use";
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ programs.rbw = {
+ enable = true;
+
+ settings = {
+ email = lib.my.mkMailAddress "bruno" "belanyi.fr";
+ inherit (cfg) pinentry;
+ };
+ };
+ };
+}
diff --git a/home/bluetooth/default.nix b/home/bluetooth/default.nix
new file mode 100644
index 0000000..2a4f613
--- /dev/null
+++ b/home/bluetooth/default.nix
@@ -0,0 +1,19 @@
+{ config, lib, ... }:
+let
+ cfg = config.my.home.bluetooth;
+in
+{
+ options.my.home.bluetooth = with lib; {
+ enable = mkEnableOption "bluetooth configuration";
+ };
+
+ config = lib.mkIf cfg.enable {
+ services.blueman-applet = {
+ enable = true;
+ };
+
+ services.mpris-proxy = {
+ enable = true;
+ };
+ };
+}
diff --git a/home/calibre/default.nix b/home/calibre/default.nix
new file mode 100644
index 0000000..e0f2069
--- /dev/null
+++ b/home/calibre/default.nix
@@ -0,0 +1,15 @@
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.my.home.calibre;
+in
+{
+ options.my.home.calibre = with lib; {
+ enable = mkEnableOption "calibre configuration";
+ };
+
+ config = lib.mkIf cfg.enable {
+ home.packages = with pkgs; [
+ calibre # NOTE: relies on my overlay to add necessary plug-in dependencies
+ ];
+ };
+}
diff --git a/home/comma/default.nix b/home/comma/default.nix
new file mode 100644
index 0000000..cc6a0ad
--- /dev/null
+++ b/home/comma/default.nix
@@ -0,0 +1,29 @@
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.my.home.comma;
+in
+{
+ options.my.home.comma = with lib; {
+ enable = my.mkDisableOption "comma configuration";
+
+ pkgsFlake = mkOption {
+ type = types.str;
+ default = "pkgs";
+ example = "nixpkgs";
+ description = ''
+ Which flake from the registry should be used with
+ nix shell.
+ '';
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ home.packages = with pkgs; [
+ ambroisie.comma
+ ];
+
+ home.sessionVariables = {
+ COMMA_PKGS_FLAKE = cfg.pkgsFlake;
+ };
+ };
+}
diff --git a/home/default.nix b/home/default.nix
index e8218e8..8ba3a8d 100644
--- a/home/default.nix
+++ b/home/default.nix
@@ -1,20 +1,47 @@
{ ... }:
{
imports = [
- ./bat.nix
- ./direnv.nix
- ./documentation.nix
+ ./aliases
+ ./atuin
+ ./bat
+ ./bitwarden
+ ./bluetooth
+ ./calibre
+ ./comma
+ ./dircolors
+ ./direnv
+ ./discord
+ ./documentation
+ ./feh
+ ./firefox
+ ./flameshot
+ ./fzf
+ ./gammastep
+ ./gdb
./git
- ./gpg.nix
- ./htop.nix
- ./jq.nix
- ./packages.nix
- ./pager.nix
- ./secrets # Home-manager specific secrets
- ./ssh.nix
- ./tmux.nix
+ ./gpg
+ ./gtk
+ ./htop
+ ./jq
+ ./mail
+ ./mpv
+ ./nix
+ ./nix-index
+ ./nixpkgs
+ ./nm-applet
+ ./packages
+ ./pager
+ ./power-alert
+ ./secrets
+ ./ssh
+ ./terminal
+ ./tmux
+ ./udiskie
./vim
- ./xdg.nix
+ ./wm
+ ./x
+ ./xdg
+ ./zathura
./zsh
];
@@ -23,4 +50,7 @@
# Who am I?
home.username = "ambroisie";
+
+ # Start services automatically
+ systemd.user.startServices = "sd-switch";
}
diff --git a/home/dircolors/default.nix b/home/dircolors/default.nix
new file mode 100644
index 0000000..876b413
--- /dev/null
+++ b/home/dircolors/default.nix
@@ -0,0 +1,15 @@
+{ config, lib, ... }:
+let
+ cfg = config.my.home.dircolors;
+in
+{
+ options.my.home.dircolors = with lib; {
+ enable = my.mkDisableOption "dircolors configuration";
+ };
+
+ config = lib.mkIf cfg.enable {
+ programs.dircolors = {
+ enable = true;
+ };
+ };
+}
diff --git a/home/direnv.nix b/home/direnv.nix
deleted file mode 100644
index d81cf46..0000000
--- a/home/direnv.nix
+++ /dev/null
@@ -1,15 +0,0 @@
-{ config, lib, ... }:
-let
- cfg = config.my.home.direnv;
-in
-{
- options.my.home.direnv = with lib.my; {
- enable = mkDisableOption "direnv configuration";
- };
-
- config.programs.direnv = lib.mkIf cfg.enable {
- enable = true;
- # A better `use_nix`
- enableNixDirenvIntegration = true;
- };
-}
diff --git a/home/direnv/default.nix b/home/direnv/default.nix
new file mode 100644
index 0000000..93a1f3b
--- /dev/null
+++ b/home/direnv/default.nix
@@ -0,0 +1,46 @@
+{ config, lib, ... }:
+let
+ cfg = config.my.home.direnv;
+in
+{
+ options.my.home.direnv = with lib; {
+ enable = my.mkDisableOption "direnv configuration";
+
+ defaultFlake = mkOption {
+ type = types.str;
+ default = "pkgs";
+ example = "nixpkgs";
+ description = ''
+ Which flake from the registry should be used for
+ use pkgs by default.
+ '';
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ programs.direnv = {
+ enable = true;
+ nix-direnv = {
+ # A better `use_nix`
+ enable = true;
+ };
+ };
+
+ xdg.configFile =
+ let
+ libDir = ./lib;
+ contents = builtins.readDir libDir;
+ names = lib.attrNames contents;
+ files = lib.filter (name: contents.${name} == "regular") names;
+ linkLibFile = name:
+ lib.nameValuePair
+ "direnv/lib/${name}"
+ { source = libDir + "/${name}"; };
+ in
+ lib.my.genAttrs' files linkLibFile;
+
+ home.sessionVariables = {
+ DIRENV_DEFAULT_FLAKE = cfg.defaultFlake;
+ };
+ };
+}
diff --git a/home/direnv/lib/nix.sh b/home/direnv/lib/nix.sh
new file mode 100644
index 0000000..2d40b20
--- /dev/null
+++ b/home/direnv/lib/nix.sh
@@ -0,0 +1,32 @@
+#shellcheck shell=bash
+
+use_pkgs() {
+ if ! has nix; then
+ # shellcheck disable=2016
+ log_error 'use_pkgs: `nix` is not in PATH'
+ return 1
+ fi
+
+ # Use user-provided default value, or fallback to nixpkgs
+ local DEFAULT_FLAKE="${DIRENV_DEFAULT_FLAKE:-nixpkgs}"
+
+ # Allow changing the default flake through a command line switch
+ if [ "$1" = "-f" ] || [ "$1" = "--flake" ]; then
+ DEFAULT_FLAKE="$2"
+ shift 2
+ fi
+
+
+ # Allow specifying a full installable, or just a package name and use the default flake
+ local packages=()
+ for pkg; do
+ if [[ $pkg =~ .*#.* ]]; then
+ packages+=("$pkg")
+ else
+ packages+=("$DEFAULT_FLAKE#$pkg")
+ fi
+ done
+
+ # shellcheck disable=2154
+ direnv_load nix shell "${packages[@]}" --command "$direnv" dump
+}
diff --git a/home/direnv/lib/postgres.sh b/home/direnv/lib/postgres.sh
new file mode 100644
index 0000000..c2e6a8f
--- /dev/null
+++ b/home/direnv/lib/postgres.sh
@@ -0,0 +1,22 @@
+#shellcheck shell=bash
+
+layout_postgres() {
+ if ! has postgres || ! has initdb; then
+ # shellcheck disable=2016
+ log_error 'layout_postgres: `postgres` and `initdb` are not in PATH'
+ return 1
+ fi
+
+ # shellcheck disable=2155
+ export PGDATA="$(direnv_layout_dir)/postgres"
+ export PGHOST="$PGDATA"
+
+ if [[ ! -d "$PGDATA" ]]; then
+ initdb
+ cat >> "$PGDATA/postgresql.conf" << EOF
+listen_addresses = ''
+unix_socket_directories = '$PGHOST'
+EOF
+ echo "CREATE DATABASE $USER;" | postgres --single -E postgres
+ fi
+}
diff --git a/home/direnv/lib/python.sh b/home/direnv/lib/python.sh
new file mode 100644
index 0000000..15a273f
--- /dev/null
+++ b/home/direnv/lib/python.sh
@@ -0,0 +1,25 @@
+#shellcheck shell=bash
+
+layout_poetry() {
+ if ! has poetry; then
+ # shellcheck disable=2016
+ log_error 'layout_poetry: `poetry` is not in PATH'
+ return 1
+ fi
+
+ if [[ ! -f pyproject.toml ]]; then
+ # shellcheck disable=2016
+ log_error 'layout_poetry: no pyproject.toml found. Use `poetry new` or `poetry init` to create one first'
+ return 1
+ fi
+
+ # create venv if it doesn't exist
+ poetry run true
+
+ # shellcheck disable=2155
+ export VIRTUAL_ENV=$(poetry env info --path)
+ export POETRY_ACTIVE=1
+ PATH_add "$VIRTUAL_ENV/bin"
+ watch_file pyproject.toml
+ watch_file poetry.lock
+}
diff --git a/home/discord/default.nix b/home/discord/default.nix
new file mode 100644
index 0000000..7348bb4
--- /dev/null
+++ b/home/discord/default.nix
@@ -0,0 +1,23 @@
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.my.home.discord;
+
+ jsonFormat = pkgs.formats.json { };
+in
+{
+ options.my.home.discord = with lib; {
+ enable = mkEnableOption "discord configuration";
+ };
+
+ config = lib.mkIf cfg.enable {
+ home.packages = with pkgs; [
+ discord
+ ];
+
+ xdg.configFile."discord/settings.json".source =
+ jsonFormat.generate "discord.json" {
+ # Do not keep me from using the app just to force an update
+ SKIP_HOST_UPDATE = true;
+ };
+ };
+}
diff --git a/home/documentation.nix b/home/documentation/default.nix
similarity index 72%
rename from home/documentation.nix
rename to home/documentation/default.nix
index e31665f..1a305e0 100644
--- a/home/documentation.nix
+++ b/home/documentation/default.nix
@@ -3,8 +3,8 @@ let
cfg = config.my.home.documentation;
in
{
- options.my.home.documentation = with lib.my; {
- enable = mkDisableOption "documentation integration";
+ options.my.home.documentation = with lib; {
+ enable = my.mkDisableOption "documentation integration";
};
# Add documentation for user packages
diff --git a/home/feh/default.nix b/home/feh/default.nix
new file mode 100644
index 0000000..3a952a2
--- /dev/null
+++ b/home/feh/default.nix
@@ -0,0 +1,13 @@
+{ config, lib, ... }:
+let
+ cfg = config.my.home.feh;
+in
+{
+ options.my.home.feh = with lib; {
+ enable = mkEnableOption "feh configuration";
+ };
+
+ config.programs.feh = lib.mkIf cfg.enable {
+ enable = true;
+ };
+}
diff --git a/home/firefox/default.nix b/home/firefox/default.nix
new file mode 100644
index 0000000..7374b63
--- /dev/null
+++ b/home/firefox/default.nix
@@ -0,0 +1,83 @@
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.my.home.firefox;
+in
+{
+ imports = [
+ ./tridactyl
+ ];
+
+ options.my.home.firefox = with lib; {
+ enable = mkEnableOption "firefox configuration";
+
+ tridactyl = {
+ enable = mkOption {
+ type = types.bool;
+ description = "tridactyl configuration";
+ example = false;
+ default = config.my.home.firefox.enable;
+ };
+ };
+
+ ff2mpv = {
+ enable = mkOption {
+ type = types.bool;
+ description = "ff2mpv configuration";
+ example = false;
+ default = config.my.home.mpv.enable;
+ };
+ };
+ };
+
+ config.programs.firefox = lib.mkIf cfg.enable {
+ enable = true;
+
+ package = pkgs.firefox.override {
+ cfg = {
+ enableTridactylNative = cfg.tridactyl.enable;
+ };
+
+ extraNativeMessagingHosts = with pkgs; ([ ]
+ # Watch videos using mpv
+ ++ lib.optional cfg.ff2mpv.enable ambroisie.ff2mpv-go
+ );
+ };
+
+ profiles = {
+ default = {
+ id = 0;
+
+ settings = {
+ "browser.bookmarks.showMobileBookmarks" = true; # Mobile bookmarks
+ "browser.download.useDownloadDir" = false; # Ask for download location
+ "browser.in-content.dark-mode" = true; # Dark mode
+ "browser.newtabpage.activity-stream.feeds.section.topstories" = false; # Disable top stories
+ "browser.newtabpage.activity-stream.feeds.sections" = false;
+ "browser.newtabpage.activity-stream.feeds.system.topstories" = false; # Disable top stories
+ "browser.newtabpage.activity-stream.section.highlights.includePocket" = false; # Disable pocket
+ "extensions.pocket.enabled" = false; # Disable pocket
+ "media.eme.enabled" = true; # Enable DRM
+ "media.gmp-widevinecdm.enabled" = true; # Enable DRM
+ "media.gmp-widevinecdm.visible" = true; # Enable DRM
+ "signon.autofillForms" = false; # Disable built-in form-filling
+ "signon.rememberSignons" = false; # Disable built-in password manager
+ "ui.systemUsesDarkTheme" = true; # Dark mode
+ };
+
+ extensions = with pkgs.nur.repos.rycee.firefox-addons; ([
+ bitwarden
+ consent-o-matic
+ form-history-control
+ reddit-comment-collapser
+ reddit-enhancement-suite
+ refined-github
+ sponsorblock
+ ublock-origin
+ ]
+ ++ lib.optional (cfg.tridactyl.enable) tridactyl
+ ++ lib.optional (cfg.ff2mpv.enable) ff2mpv
+ );
+ };
+ };
+ };
+}
diff --git a/home/firefox/tridactyl/default.nix b/home/firefox/tridactyl/default.nix
new file mode 100644
index 0000000..35b58c2
--- /dev/null
+++ b/home/firefox/tridactyl/default.nix
@@ -0,0 +1,28 @@
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.my.home.firefox.tridactyl;
+
+ term = config.my.home.terminal.program;
+
+ vimCommandLine = {
+ alacritty = ''-e "vim" "%f" "+normal!%lGzv%c|"'';
+ # Termite wants the whole command in a single argument...
+ termite = ''-e "vim %f '+normal!%lGzv%c|'"'';
+ };
+in
+{
+ config = lib.mkIf cfg.enable {
+ xdg.configFile."tridactyl/tridactylrc".source = pkgs.substituteAll {
+ src = ./tridactylrc;
+
+ editorcmd = lib.concatStringsSep " " [
+ # Use my configured terminal
+ term
+ # Make it easy to pick out with a window class name
+ "--class tridactyl_editor"
+ # Open vim with the cursor in the correct position
+ vimCommandLine.${term}
+ ];
+ };
+ };
+}
diff --git a/home/firefox/tridactyl/tridactylrc b/home/firefox/tridactyl/tridactylrc
new file mode 100644
index 0000000..31d3cb7
--- /dev/null
+++ b/home/firefox/tridactyl/tridactylrc
@@ -0,0 +1,82 @@
+" Shamelessly taken from bovine3dom's example configuration file from the docs
+
+" Basics {{{
+" Use dark color scheme
+colorscheme dark
+
+" Make tridactyl open Vim in my prefered terminal
+set editorcmd @editorcmd@
+
+" Remove editor file after use
+alias editor_rm composite editor | jsb -p tri.native.run(`rm -f '${JS_ARG[0]}'`)
+bind --mode=insert editor_rm
+bind --mode=input editor_rm
+" }}}
+
+" Binds {{{
+" Reddit et al. {{{
+" Toggle comments on Reddit, Hacker News, Lobste.rs
+bind ;c hint -Jc [class*="expand"],[class*="togg"],[class="comment_folder"]
+
+" Make `gu` take me back to subreddit from comments
+bindurl reddit.com gu urlparent 3
+
+" Only hint search results on Google
+bindurl www.google.com f hint -Jc #search div:not(.action-menu) > a
+bindurl www.google.com F hint -Jbc #search div:not(.action-menu) > a
+
+" Only hint search results on DuckDuckGo
+bindurl ^https://duckduckgo.com f hint -Jc [data-testid="result-title-a"]
+bindurl ^https://duckduckgo.com F hint -Jbc [data-testid="result-title-a"]
+
+" Only hint item pages on Hacker News
+bindurl news.ycombinator.com ;f hint -Jc .age > a
+bindurl news.ycombinator.com ;F hint -Jtc .age > a
+" }}}
+
+" Better bindings {{{
+" Handy multiwindow binds
+bind gd tabdetach
+bind gD composite tabduplicate; tabdetach
+
+" Duplicate a tab without detaching window
+bind d tabduplicate
+
+" Make yy use canonical links on the few websites that support them
+bind yy clipboard yankcanon
+" }}}
+
+" Search {{{
+" Case insensitive only if fully lowercase
+set findcase smart
+
+" Search forward/backward
+bind / fillcmdline find
+bind ? fillcmdline find -?
+
+" Go to next/previous match
+bind n findnext 1
+bind N findnext -1
+
+" Because :nohls never works
+bind nohlsearch
+
+" Use browser's native find when using Ctrl-F
+unbind
+" }}}
+" }}}
+
+" Redirections {{{
+" Always redirect Reddit to the old site
+autocmd DocStart ^http(s?)://www.reddit.com js tri.excmds.urlmodify("-t", "www", "old")
+" Use a better Twitter front-end
+autocmd DocStart ^http(s?)://twitter.com js tri.excmds.urlmodify("-t", "twitter.com", "nitter.net")
+" }}}
+
+" Disabled websites {{{
+blacklistadd netflix.com
+blacklistadd primevideo.com
+blacklistadd jellyfin.belanyi.fr
+" }}}
+
+" vim: set filetype=vim foldmethod=marker:
diff --git a/home/flameshot/default.nix b/home/flameshot/default.nix
new file mode 100644
index 0000000..a9a60a8
--- /dev/null
+++ b/home/flameshot/default.nix
@@ -0,0 +1,13 @@
+{ config, lib, ... }:
+let
+ cfg = config.my.home.flameshot;
+in
+{
+ options.my.home.flameshot = with lib; {
+ enable = mkEnableOption "flameshot configuration";
+ };
+
+ config.services.flameshot = lib.mkIf cfg.enable {
+ enable = true;
+ };
+}
diff --git a/home/fzf/default.nix b/home/fzf/default.nix
new file mode 100644
index 0000000..b7e308f
--- /dev/null
+++ b/home/fzf/default.nix
@@ -0,0 +1,15 @@
+{ config, lib, ... }:
+let
+ cfg = config.my.home.fzf;
+in
+{
+ options.my.home.fzf = with lib; {
+ enable = my.mkDisableOption "fzf configuration";
+ };
+
+ config = lib.mkIf cfg.enable {
+ programs.fzf = {
+ enable = true;
+ };
+ };
+}
diff --git a/home/gammastep/default.nix b/home/gammastep/default.nix
new file mode 100644
index 0000000..3e20094
--- /dev/null
+++ b/home/gammastep/default.nix
@@ -0,0 +1,44 @@
+{ config, lib, ... }:
+let
+ cfg = config.my.home.gammastep;
+
+ mkTempOption = with lib; description: default: mkOption {
+ inherit description default;
+ type = types.int;
+ example = 1000;
+ };
+
+ mkTimeOption = with lib; description: default: mkOption {
+ inherit description default;
+ type = types.str;
+ example = "12:00-14:00";
+ };
+in
+{
+ options.my.home.gammastep = with lib; {
+ enable = mkEnableOption "gammastep configuration";
+
+ temperature = {
+ day = mkTempOption "Colour temperature to use during the day" 6500;
+ night = mkTempOption "Colour temperature to use during the night" 2000;
+ };
+
+ times = {
+ dawn = mkTimeOption "Dawn time" "6:00-7:30";
+ dusk = mkTimeOption "Dusk time" "18:30-20:00";
+ };
+ };
+
+ config.services.gammastep = lib.mkIf cfg.enable {
+ enable = true;
+
+ tray = true;
+
+ dawnTime = cfg.times.dawn;
+ duskTime = cfg.times.dusk;
+
+ temperature = {
+ inherit (cfg.temperature) day night;
+ };
+ };
+}
diff --git a/home/gdb/default.nix b/home/gdb/default.nix
new file mode 100644
index 0000000..c498048
--- /dev/null
+++ b/home/gdb/default.nix
@@ -0,0 +1,38 @@
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.my.home.gdb;
+in
+{
+ options.my.home.gdb = with lib; {
+ enable = my.mkDisableOption "gdb configuration";
+
+ rr = {
+ enable = my.mkDisableOption "rr configuration";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.rr;
+ defaultText = literalExample "pkgs.rr";
+ description = ''
+ Package providing rr
+ '';
+ };
+ };
+ };
+
+ config = lib.mkMerge [
+ (lib.mkIf cfg.enable {
+ home.packages = with pkgs; [
+ gdb
+ ];
+
+ xdg.configFile."gdb/gdbinit".source = ./gdbinit;
+ })
+
+ (lib.mkIf cfg.rr.enable {
+ home.packages = [
+ cfg.rr.package
+ ];
+ })
+ ];
+}
diff --git a/home/gdb/gdbinit b/home/gdb/gdbinit
new file mode 100644
index 0000000..86e8c3c
--- /dev/null
+++ b/home/gdb/gdbinit
@@ -0,0 +1,24 @@
+ # Keep a history of all commands in each directory
+set history save on
+
+# Enable those pretty-printers
+enable pretty-printer
+
+# Pretty formatting of structures
+set print pretty on
+# Show derived type based on VTable
+set print object on
+# Show static members
+set print static-members on
+# Show VTable
+set print vtbl on
+# Demangle types
+set print demangle on
+
+# Read python scrips in the load path
+set auto-load python-scripts
+
+# Allow autoloading project-local .gdbinit files
+add-auto-load-safe-path ~/git/
+# Allow autoloading from the Nix store
+add-auto-load-safe-path /nix/store
diff --git a/home/git/default.ignore b/home/git/default.ignore
index a169a28..d80996e 100644
--- a/home/git/default.ignore
+++ b/home/git/default.ignore
@@ -24,3 +24,11 @@ compile_commands.json
# Swap and backup files
*~
~.swp
+
+# Direnv files
+.envrc
+.direnv/
+
+# Project-local neovim configuration files
+.nvim.lua
+.nvimrc
diff --git a/home/git/default.nix b/home/git/default.nix
index da5efae..4dba01e 100644
--- a/home/git/default.nix
+++ b/home/git/default.nix
@@ -1,34 +1,73 @@
{ config, pkgs, lib, ... }:
let
cfg = config.my.home.git;
+
+ inherit (lib.my) mkMailAddress;
in
{
- options.my.home.git = with lib.my; {
- enable = mkDisableOption "git configuration";
+ options.my.home.git = with lib; {
+ enable = my.mkDisableOption "git configuration";
};
+ config.home.packages = with pkgs; lib.mkIf cfg.enable [
+ git-absorb
+ git-revise
+ tig
+ ];
+
config.programs.git = lib.mkIf cfg.enable {
enable = true;
# Who am I?
- userEmail = "bruno@belanyi.fr";
+ userEmail = mkMailAddress "bruno" "belanyi.fr";
userName = "Bruno BELANYI";
# I want the full experience
- package = pkgs.gitAndTools.gitFull;
+ package = pkgs.gitFull;
aliases = {
- lol = "log --graph --decorate --pretty=oneline --abbrev-commit";
+ git = "!git";
+ lol = "log --graph --decorate --pretty=oneline --abbrev-commit --topo-order";
lola = "lol --all";
assume = "update-index --assume-unchanged";
unassume = "update-index --no-assume-unchanged";
assumed = "!git ls-files -v | grep ^h | cut -c 3-";
+ pick = "log -p -G";
push-new = "!git push -u origin "
+ ''"$(git branch | grep '^* ' | cut -f2- -d' ')"'';
+ root = "git rev-parse --show-toplevel";
};
lfs.enable = true;
+ delta = {
+ enable = true;
+
+ options = {
+ features = "diff-highlight decorations";
+
+ # Less jarring style for `diff-highlight` emulation
+ diff-highlight = {
+ minus-style = "red";
+ minus-non-emph-style = "red";
+ minus-emph-style = "bold red 52";
+
+ plus-style = "green";
+ plus-non-emph-style = "green";
+ plus-emph-style = "bold green 22";
+
+ whitespace-error-style = "reverse red";
+ };
+
+ # Personal preference for easier reading
+ decorations = {
+ commit-style = "raw"; # Do not recolor meta information
+ keep-plus-minus-markers = true;
+ paging = "always";
+ };
+ };
+ };
+
# There's more
extraConfig = {
# Makes it a bit more readable
@@ -62,13 +101,6 @@ in
whitespace = "red reverse";
};
- "color.diff-highlight" = {
- oldNormal = "red bold";
- oldHighlight = "red bold 52";
- newNormal = "green bold";
- newHighlight = "green bold 22";
- };
-
commit = {
# Show my changes when writing the message
verbose = true;
@@ -89,15 +121,14 @@ in
defaultBranch = "main";
};
- pager =
- let
- diff-highlight = "${pkgs.gitAndTools.gitFull}/share/git/contrib/diff-highlight/diff-highlight";
- in
- {
- diff = "${diff-highlight} | less";
- log = "${diff-highlight} | less";
- show = "${diff-highlight} | less";
- };
+ # Local configuration, not-versioned
+ include = {
+ path = "config.local";
+ };
+
+ merge = {
+ conflictStyle = "zdiff3";
+ };
pull = {
# Avoid useless merge commits
@@ -114,11 +145,42 @@ in
autoSquash = true;
autoStash = true;
};
+
+ url = {
+ "git@git.belanyi.fr:" = {
+ insteadOf = "https://git.belanyi.fr/";
+ };
+
+ "git@github.com:" = {
+ insteadOf = "https://github.com/";
+ };
+
+ "git@gitlab.com:" = {
+ insteadOf = "https://gitlab.com/";
+ };
+ };
};
# Multiple identities
includes = [
- { path = ./epita.config; condition = "gitdir:~/git/EPITA/"; }
+ {
+ condition = "gitdir:~/git/EPITA/";
+ contents = {
+ user = {
+ name = "Bruno BELANYI";
+ email = mkMailAddress "bruno.belanyi" "epita.fr";
+ };
+ };
+ }
+ {
+ condition = "gitdir:~/git/work/";
+ contents = {
+ user = {
+ name = "Bruno BELANYI";
+ email = mkMailAddress "ambroisie" "google.com";
+ };
+ };
+ }
];
ignores =
diff --git a/home/git/epita.config b/home/git/epita.config
deleted file mode 100644
index a6e8cf4..0000000
--- a/home/git/epita.config
+++ /dev/null
@@ -1,4 +0,0 @@
-[user]
- email = bruno.belanyi@epita.fr
- name = Bruno BELANYI
-# vim: set ft=gitconfig:
diff --git a/home/gpg.nix b/home/gpg.nix
deleted file mode 100644
index 548f90b..0000000
--- a/home/gpg.nix
+++ /dev/null
@@ -1,24 +0,0 @@
-{ config, lib, ... }:
-let
- cfg = config.my.home.gpg;
-in
-{
- options.my.home.gpg = with lib.my; {
- enable = mkDisableOption "gpg configuration";
- };
-
- config = lib.mkIf cfg.enable {
- programs.gpg = {
- enable = true;
- };
-
- services.gpg-agent = {
- enable = true;
- enableSshSupport = true; # One agent to rule them all
- pinentryFlavor = "tty";
- extraConfig = ''
- allow-loopback-pinentry
- '';
- };
- };
-}
diff --git a/home/gpg/default.nix b/home/gpg/default.nix
new file mode 100644
index 0000000..7eadf48
--- /dev/null
+++ b/home/gpg/default.nix
@@ -0,0 +1,36 @@
+{ config, lib, ... }:
+let
+ cfg = config.my.home.gpg;
+in
+{
+ options.my.home.gpg = with lib; {
+ enable = my.mkDisableOption "gpg configuration";
+
+ pinentry = mkOption {
+ type = types.str;
+ default = "tty";
+ example = "gtk2";
+ description = "Which pinentry interface to use";
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ programs.gpg = {
+ enable = true;
+ };
+
+ services.gpg-agent = {
+ enable = true;
+ enableSshSupport = true; # One agent to rule them all
+ pinentryFlavor = cfg.pinentry;
+ extraConfig = ''
+ allow-loopback-pinentry
+ '';
+ };
+
+ home.shellAliases = {
+ # Sometime `gpg-agent` errors out...
+ reset-agent = "gpg-connect-agent updatestartuptty /bye";
+ };
+ };
+}
diff --git a/home/gtk/default.nix b/home/gtk/default.nix
new file mode 100644
index 0000000..62d3f81
--- /dev/null
+++ b/home/gtk/default.nix
@@ -0,0 +1,33 @@
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.my.home.gtk;
+in
+{
+ options.my.home.gtk = with lib; {
+ enable = mkEnableOption "GTK configuration";
+ };
+
+ config.gtk = lib.mkIf cfg.enable {
+ enable = true;
+
+ font = {
+ package = pkgs.dejavu_fonts;
+ name = "DejaVu Sans";
+ };
+
+ gtk2 = {
+ # That sweet, sweet clean home that I am always aiming for...
+ configLocation = "${config.xdg.configHome}/gtk-2.0/gtkrc";
+ };
+
+ iconTheme = {
+ package = pkgs.gnome.gnome-themes-extra;
+ name = "Adwaita";
+ };
+
+ theme = {
+ package = pkgs.gnome.gnome-themes-extra;
+ name = "Adwaita";
+ };
+ };
+}
diff --git a/home/htop.nix b/home/htop/default.nix
similarity index 60%
rename from home/htop.nix
rename to home/htop/default.nix
index c4b33a9..570fa47 100644
--- a/home/htop.nix
+++ b/home/htop/default.nix
@@ -3,8 +3,8 @@ let
cfg = config.my.home.htop;
in
{
- options.my.home.htop = with lib.my; {
- enable = mkDisableOption "htop configuration";
+ options.my.home.htop = with lib; {
+ enable = my.mkDisableOption "htop configuration";
};
config.programs.htop = lib.mkIf cfg.enable {
diff --git a/home/jq.nix b/home/jq/default.nix
similarity index 78%
rename from home/jq.nix
rename to home/jq/default.nix
index d8bd59b..57e266f 100644
--- a/home/jq.nix
+++ b/home/jq/default.nix
@@ -3,8 +3,8 @@ let
cfg = config.my.home.jq;
in
{
- options.my.home.jq = with lib.my; {
- enable = mkDisableOption "jq configuration";
+ options.my.home.jq = with lib; {
+ enable = my.mkDisableOption "jq configuration";
};
config.programs.jq = lib.mkIf cfg.enable {
diff --git a/home/mail/accounts/default.nix b/home/mail/accounts/default.nix
new file mode 100644
index 0000000..e7663d8
--- /dev/null
+++ b/home/mail/accounts/default.nix
@@ -0,0 +1,94 @@
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.my.home.mail;
+
+ inherit (lib.my) mkMailAddress;
+
+ mkConfig = { domain, address, passName, aliases ? [ ], primary ? false }: {
+ realName = lib.mkDefault "Bruno BELANYI";
+ userName = lib.mkDefault (mkMailAddress address domain);
+ passwordCommand =
+ lib.mkDefault [ (lib.getExe pkgs.ambroisie.rbw-pass) "Mail" passName ];
+
+ address = mkMailAddress address domain;
+ aliases = builtins.map (lib.flip mkMailAddress domain) aliases;
+
+ inherit primary;
+
+ himalaya = {
+ enable = cfg.himalaya.enable;
+ # FIXME: try to actually configure it at some point
+ backend = "imap";
+ sender = "smtp";
+ };
+
+ msmtp = {
+ enable = cfg.msmtp.enable;
+ };
+ };
+
+ migaduConfig = {
+ imap = {
+ host = "imap.migadu.com";
+ port = 993;
+ tls = {
+ enable = true;
+ };
+ };
+ smtp = {
+ host = "smtp.migadu.com";
+ port = 465;
+ tls = {
+ enable = true;
+ };
+ };
+ };
+
+ gmailConfig = {
+ flavor = "gmail.com";
+ folders = {
+ drafts = "[Gmail]/Drafts";
+ sent = "[Gmail]/Sent Mail";
+ trash = "[Gmail]/Trash";
+ };
+ };
+
+ office365Config = {
+ flavor = "outlook.office365.com";
+ };
+in
+{
+ config.accounts.email.accounts = {
+ personal = lib.mkMerge [
+ # Common configuraton
+ (mkConfig {
+ domain = "belanyi.fr";
+ address = "bruno";
+ passName = "Migadu";
+ aliases = [ "admin" "postmaster" ];
+ primary = true; # This is my primary email
+ })
+ migaduConfig
+ ];
+
+ gmail = lib.mkMerge [
+ # Common configuraton
+ (mkConfig {
+ domain = "gmail.com";
+ address = "brunobelanyi";
+ passName = "GMail";
+ })
+ gmailConfig
+ ];
+
+ epita = lib.mkMerge [
+ # Common configuration
+ (mkConfig {
+ domain = "epita.fr";
+ address = "bruno.belanyi";
+ passName = "EPITA";
+ })
+ office365Config
+ ];
+ };
+}
diff --git a/home/mail/default.nix b/home/mail/default.nix
new file mode 100644
index 0000000..14f086a
--- /dev/null
+++ b/home/mail/default.nix
@@ -0,0 +1,31 @@
+{ config, lib, ... }:
+let
+ cfg = config.my.home.mail;
+
+ mkRelatedOption = desc: lib.mkEnableOption desc // { default = cfg.enable; };
+in
+{
+ imports = [
+ ./accounts
+ ./himalaya
+ ./msmtp
+ ];
+
+ options.my.home.mail = with lib; {
+ enable = my.mkDisableOption "email configuration";
+
+ himalaya = {
+ enable = mkEnableOption "himalaya configuration";
+ };
+
+ msmtp = {
+ enable = mkRelatedOption "msmtp configuration";
+ };
+ };
+
+ config = {
+ accounts.email = {
+ maildirBasePath = "mail";
+ };
+ };
+}
diff --git a/home/mail/himalaya/default.nix b/home/mail/himalaya/default.nix
new file mode 100644
index 0000000..849a415
--- /dev/null
+++ b/home/mail/himalaya/default.nix
@@ -0,0 +1,23 @@
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.my.home.mail.himalaya;
+in
+{
+ config.programs.himalaya = lib.mkIf cfg.enable {
+ enable = true;
+
+ settings = {
+ notify-cmd =
+ let
+ notify-send = lib.getExe pkgs.libnotify;
+ in
+ pkgs.writeScript "mail-notifier" ''
+ SENDER="$1"
+ SUBJECT="$2"
+ ${notify-send} \
+ -c himalaya \
+ -- "$(printf 'Received email from %s\n\n%s' "$SENDER" "$SUBJECT")"
+ '';
+ };
+ };
+}
diff --git a/home/mail/msmtp/default.nix b/home/mail/msmtp/default.nix
new file mode 100644
index 0000000..c469982
--- /dev/null
+++ b/home/mail/msmtp/default.nix
@@ -0,0 +1,9 @@
+{ config, lib, ... }:
+let
+ cfg = config.my.home.mail.msmtp;
+in
+{
+ config.programs.msmtp = lib.mkIf cfg.enable {
+ enable = true;
+ };
+}
diff --git a/home/mpv/default.nix b/home/mpv/default.nix
new file mode 100644
index 0000000..9aef379
--- /dev/null
+++ b/home/mpv/default.nix
@@ -0,0 +1,19 @@
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.my.home.mpv;
+in
+{
+ options.my.home.mpv = with lib; {
+ enable = mkEnableOption "mpv configuration";
+ };
+
+ config = lib.mkIf cfg.enable {
+ programs.mpv = {
+ enable = true;
+
+ scripts = [
+ pkgs.mpvScripts.mpris # Allow controlling using media keys
+ ];
+ };
+ };
+}
diff --git a/home/nix-index/default.nix b/home/nix-index/default.nix
new file mode 100644
index 0000000..383621e
--- /dev/null
+++ b/home/nix-index/default.nix
@@ -0,0 +1,13 @@
+{ config, lib, ... }:
+let
+ cfg = config.my.home.nix-index;
+in
+{
+ options.my.home.nix-index = with lib; {
+ enable = my.mkDisableOption "nix-index configuration";
+ };
+
+ config.programs.nix-index = lib.mkIf cfg.enable {
+ enable = true;
+ };
+}
diff --git a/home/nix/default.nix b/home/nix/default.nix
new file mode 100644
index 0000000..9ccbdc5
--- /dev/null
+++ b/home/nix/default.nix
@@ -0,0 +1,82 @@
+# Nix related settings
+{ config, inputs, lib, pkgs, ... }:
+let
+ cfg = config.my.home.nix;
+
+ channels = lib.my.merge [
+ {
+ # Allow me to use my custom package using `nix run self#pkg`
+ self = inputs.self;
+ # Add NUR to run some packages that are only present there
+ nur = inputs.nur;
+ # Use pinned nixpkgs when using `nix run pkgs#`
+ pkgs = inputs.nixpkgs;
+ }
+ (lib.optionalAttrs cfg.overrideNixpkgs {
+ # ... And with `nix run nixpkgs#`
+ nixpkgs = inputs.nixpkgs;
+ })
+ ];
+in
+{
+ options.my.home.nix = with lib; {
+ enable = my.mkDisableOption "nix configuration";
+
+ linkInputs = my.mkDisableOption "link inputs to `$XDG_CONFIG_HOME/nix/inputs`";
+
+ addToRegistry = my.mkDisableOption "add inputs and self to registry";
+
+ addToNixPath = my.mkDisableOption "add inputs and self to nix path";
+
+ overrideNixpkgs = my.mkDisableOption "point nixpkgs to pinned system version";
+ };
+
+ config = lib.mkIf cfg.enable (lib.mkMerge [
+ {
+ assertions = [
+ {
+ assertion = cfg.addToNixPath -> cfg.linkInputs;
+ message = ''
+ enabling `my.home.nix.addToNixPath` needs to have
+ `my.home.nix.linkInputs = true`
+ '';
+ }
+ ];
+ }
+
+ {
+ nix = {
+ package = lib.mkDefault pkgs.nix; # NixOS module sets it unconditionally
+
+ settings = {
+ experimental-features = [ "nix-command" "flakes" ];
+ };
+ };
+ }
+
+ (lib.mkIf cfg.addToRegistry {
+ nix.registry =
+ let
+ makeEntry = v: { flake = v; };
+ makeEntries = lib.mapAttrs (lib.const makeEntry);
+ in
+ makeEntries channels;
+ })
+
+ (lib.mkIf cfg.linkInputs {
+ xdg.configFile =
+ let
+ makeLink = n: v: {
+ name = "nix/inputs/${n}";
+ value = { source = v.outPath; };
+ };
+ makeLinks = lib.mapAttrs' makeLink;
+ in
+ makeLinks channels;
+ })
+
+ (lib.mkIf cfg.addToNixPath {
+ home.sessionVariables.NIX_PATH = "${config.xdg.configHome}/nix/inputs\${NIX_PATH:+:$NIX_PATH}";
+ })
+ ]);
+}
diff --git a/home/nixpkgs/default.nix b/home/nixpkgs/default.nix
new file mode 100644
index 0000000..720fc9b
--- /dev/null
+++ b/home/nixpkgs/default.nix
@@ -0,0 +1,20 @@
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.my.home.nixpkgs;
+in
+{
+ options.my.home.nixpkgs = with lib; {
+ enable = mkEnableOption "nixpkgs configuration";
+ };
+
+ config = lib.mkIf cfg.enable {
+ home.packages = with pkgs; [
+ nixpkgs-review
+ ];
+
+ home.sessionVariables = {
+ GITHUB_TOKEN = ''$(cat "${config.age.secrets."github/token".path}")'';
+ GITHUB_API_TOKEN = ''$(cat "${config.age.secrets."github/token".path}")'';
+ };
+ };
+}
diff --git a/home/nm-applet/default.nix b/home/nm-applet/default.nix
new file mode 100644
index 0000000..b8637f7
--- /dev/null
+++ b/home/nm-applet/default.nix
@@ -0,0 +1,13 @@
+{ config, lib, ... }:
+let
+ cfg = config.my.home.nm-applet;
+in
+{
+ options.my.home.nm-applet = with lib; {
+ enable = mkEnableOption "network-manager-applet configuration";
+ };
+
+ config.services.network-manager-applet = lib.mkIf cfg.enable {
+ enable = true;
+ };
+}
diff --git a/home/packages.nix b/home/packages.nix
deleted file mode 100644
index b951f9e..0000000
--- a/home/packages.nix
+++ /dev/null
@@ -1,20 +0,0 @@
-{ config, lib, pkgs, ... }:
-let
- cfg = config.my.home.packages;
-in
-{
- options.my.home.packages = with lib.my; {
- enable = mkDisableOption "user packages";
- };
-
- config.home.packages = with pkgs; lib.mkIf cfg.enable [
- # Git related
- gitAndTools.git-absorb
- gitAndTools.git-revise
- gitAndTools.tig
- # Dev work
- rr
- # Terminal prettiness
- termite.terminfo
- ];
-}
diff --git a/home/packages/default.nix b/home/packages/default.nix
new file mode 100644
index 0000000..0cfa3b3
--- /dev/null
+++ b/home/packages/default.nix
@@ -0,0 +1,26 @@
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.my.home.packages;
+in
+{
+ options.my.home.packages = with lib; {
+ enable = my.mkDisableOption "user packages";
+
+ additionalPackages = mkOption {
+ type = with types; listOf package;
+ default = [ ];
+ example = literalExample ''
+ with pkgs; [
+ quasselClient
+ ]
+ '';
+ };
+ };
+
+ config.home.packages = with pkgs; lib.mkIf cfg.enable ([
+ fd
+ file
+ mosh
+ ripgrep
+ ] ++ cfg.additionalPackages);
+}
diff --git a/home/pager.nix b/home/pager.nix
deleted file mode 100644
index 54ea3c4..0000000
--- a/home/pager.nix
+++ /dev/null
@@ -1,18 +0,0 @@
-{ config, lib, ... }:
-let
- cfg = config.my.home.pager;
-in
-{
- options.my.home.pager = with lib.my; {
- enable = mkDisableOption "pager configuration";
- };
-
- config.programs.lesspipe.enable = cfg.enable;
-
- config.home.sessionVariables = lib.mkIf cfg.enable {
- # My default pager
- PAGER = "less";
- # Clear the screen on start and exit
- LESS = "-R -+X -c";
- };
-}
diff --git a/home/pager/default.nix b/home/pager/default.nix
new file mode 100644
index 0000000..aa72587
--- /dev/null
+++ b/home/pager/default.nix
@@ -0,0 +1,21 @@
+{ config, lib, ... }:
+let
+ cfg = config.my.home.pager;
+in
+{
+ options.my.home.pager = with lib; {
+ enable = my.mkDisableOption "pager configuration";
+ };
+
+
+ config = lib.mkIf cfg.enable {
+ home.sessionVariables = {
+ # My default pager
+ PAGER = "less";
+ # Clear the screen on start and exit
+ LESS = "-R -+X -c";
+ # Better XDG compliance
+ LESSHISTFILE = "${config.xdg.dataHome}/less/history";
+ };
+ };
+}
diff --git a/home/power-alert/default.nix b/home/power-alert/default.nix
new file mode 100644
index 0000000..8dbb5e6
--- /dev/null
+++ b/home/power-alert/default.nix
@@ -0,0 +1,15 @@
+{ config, lib, ... }:
+let
+ cfg = config.my.home.power-alert;
+in
+{
+ options.my.home.power-alert = with lib; {
+ enable = mkEnableOption "power-alert configuration";
+ };
+
+ config = lib.mkIf cfg.enable {
+ services.poweralertd = {
+ enable = true;
+ };
+ };
+}
diff --git a/home/secrets/.gitattributes b/home/secrets/.gitattributes
deleted file mode 100644
index a741d4d..0000000
--- a/home/secrets/.gitattributes
+++ /dev/null
@@ -1,3 +0,0 @@
-* filter=git-crypt diff=git-crypt
-.gitattributes !filter !diff
-/default.nix !filter !diff
diff --git a/home/secrets/canary b/home/secrets/canary
deleted file mode 100644
index e910ea3..0000000
Binary files a/home/secrets/canary and /dev/null differ
diff --git a/home/secrets/default.nix b/home/secrets/default.nix
index 3624472..7c0c0a1 100644
--- a/home/secrets/default.nix
+++ b/home/secrets/default.nix
@@ -1,19 +1,25 @@
-{ lib, ... }:
+{ config, inputs, lib, options, ... }:
-with lib;
-let
- canaryHash = builtins.hashFile "sha256" ./canary;
- expectedHash =
- "9df8c065663197b5a1095122d48e140d3677d860343256abd5ab6e4fb4c696ab";
-in
-if canaryHash != expectedHash then
- abort "Secrets are not readable. Have you run `git-crypt unlock`?"
-else {
- options.my.secrets = mkOption {
- type = types.attrs;
- };
+{
+ imports = [
+ inputs.agenix.homeManagerModules.age
+ ];
- config.my.secrets = {
- # Home-manager secrets go here
+ config.age = {
+ secrets =
+ let
+ toName = lib.removeSuffix ".age";
+ toSecret = name: { ... }: {
+ file = ./. + "/${name}";
+ };
+ convertSecrets = n: v: lib.nameValuePair (toName n) (toSecret n v);
+ secrets = import ./secrets.nix;
+ in
+ lib.mapAttrs' convertSecrets secrets;
+
+ # Add my usual agenix key to the defaults
+ identityPaths = options.age.identityPaths.default ++ [
+ "${config.home.homeDirectory}/.ssh/agenix"
+ ];
};
}
diff --git a/home/secrets/github/token.age b/home/secrets/github/token.age
new file mode 100644
index 0000000..1d36ccd
Binary files /dev/null and b/home/secrets/github/token.age differ
diff --git a/home/secrets/secrets.nix b/home/secrets/secrets.nix
new file mode 100644
index 0000000..f474342
--- /dev/null
+++ b/home/secrets/secrets.nix
@@ -0,0 +1,9 @@
+# Common secrets
+let
+ keys = import ../../keys;
+
+ all = builtins.attrValues keys.users;
+in
+{
+ "github/token.age".publicKeys = all;
+}
diff --git a/home/ssh.nix b/home/ssh/default.nix
similarity index 73%
rename from home/ssh.nix
rename to home/ssh/default.nix
index 22f85d3..123190f 100644
--- a/home/ssh.nix
+++ b/home/ssh/default.nix
@@ -3,13 +3,18 @@ let
cfg = config.my.home.ssh;
in
{
- options.my.home.ssh = with lib.my; {
- enable = mkDisableOption "ssh configuration";
+ options.my.home.ssh = with lib; {
+ enable = my.mkDisableOption "ssh configuration";
};
- config.programs.ssh = {
+ config.programs.ssh = lib.mkIf cfg.enable {
enable = true;
+ includes = [
+ # Local configuration, not-versioned
+ "config.local"
+ ];
+
matchBlocks = {
"github.com" = {
hostname = "github.com";
@@ -29,8 +34,8 @@ in
user = "git";
};
- "gitea.belanyi.fr" = {
- hostname = "gitea.belanyi.fr";
+ "git.belanyi.fr" = {
+ hostname = "git.belanyi.fr";
identityFile = "~/.ssh/shared_rsa";
user = "git";
};
diff --git a/home/terminal/alacritty/default.nix b/home/terminal/alacritty/default.nix
new file mode 100644
index 0000000..daf3e80
--- /dev/null
+++ b/home/terminal/alacritty/default.nix
@@ -0,0 +1,52 @@
+{ config, lib, ... }:
+let
+ cfg = config.my.home.terminal;
+in
+{
+ config = lib.mkIf (cfg.program == "alacritty") {
+ programs.alacritty = {
+ enable = true;
+
+ settings = {
+ font = {
+ size = 5.5;
+ };
+
+ colors = {
+ primary = {
+ background = cfg.colors.background;
+ foreground = cfg.colors.foreground;
+
+ bright_foreground = cfg.colors.foregroundBold;
+ };
+
+ cursor = {
+ cursor = cfg.colors.cursor;
+ };
+
+ normal = {
+ black = cfg.colors.black;
+ red = cfg.colors.red;
+ green = cfg.colors.green;
+ yellow = cfg.colors.yellow;
+ blue = cfg.colors.blue;
+ magenta = cfg.colors.magenta;
+ cyan = cfg.colors.cyan;
+ white = cfg.colors.white;
+ };
+
+ bright = {
+ black = cfg.colors.blackBold;
+ red = cfg.colors.redBold;
+ green = cfg.colors.greenBold;
+ yellow = cfg.colors.yellowBold;
+ blue = cfg.colors.blueBold;
+ magenta = cfg.colors.magentaBold;
+ cyan = cfg.colors.cyanBold;
+ white = cfg.colors.whiteBold;
+ };
+ };
+ };
+ };
+ };
+}
diff --git a/home/terminal/default.nix b/home/terminal/default.nix
new file mode 100644
index 0000000..20f36b5
--- /dev/null
+++ b/home/terminal/default.nix
@@ -0,0 +1,62 @@
+{ config, lib, ... }:
+let
+ mkColorOption = with lib; description: default: mkOption {
+ inherit description default;
+ example = "#abcdef";
+ type = types.strMatching "#[0-9a-f]{6}";
+ };
+
+ cfg = config.my.home.terminal;
+in
+{
+ imports = [
+ ./alacritty
+ ./termite
+ ];
+
+ options.my.home = with lib; {
+ terminal = {
+ program = mkOption {
+ type = with types; nullOr (enum [ "alacritty" "termite" ]);
+ default = null;
+ example = "termite";
+ description = "Which terminal to use for home session";
+ };
+
+ colors = {
+ background = mkColorOption "Background color" "#161616";
+ foreground = mkColorOption "Foreground color" "#ffffff";
+ foregroundBold = mkColorOption "Foreground bold color" "#ffffff";
+ cursor = mkColorOption "Cursor color" "#ffffff";
+
+ black = mkColorOption "Black" "#222222";
+ blackBold = mkColorOption "Black bold" "#666666";
+
+ red = mkColorOption "Red" "#e84f4f";
+ redBold = mkColorOption "Red bold" "#d23d3d";
+
+ green = mkColorOption "Green" "#b7ce42";
+ greenBold = mkColorOption "Green bold" "#bde077";
+
+ yellow = mkColorOption "Yellow" "#fea63c";
+ yellowBold = mkColorOption "Yellow bold" "#ffe863";
+
+ blue = mkColorOption "Blue" "#66aabb";
+ blueBold = mkColorOption "Blue bold" "#aaccbb";
+
+ magenta = mkColorOption "Magenta" "#b7416e";
+ magentaBold = mkColorOption "Magenta bold" "#e16a98";
+
+ cyan = mkColorOption "Cyan" "#6d878d";
+ cyanBold = mkColorOption "Cyan bold" "#42717b";
+
+ white = mkColorOption "White" "#dddddd";
+ whiteBold = mkColorOption "White bold" "#cccccc";
+ };
+ };
+ };
+
+ config.home.sessionVariables = lib.mkIf (cfg.program != null) {
+ TERMINAL = cfg.program;
+ };
+}
diff --git a/home/terminal/termite/default.nix b/home/terminal/termite/default.nix
new file mode 100644
index 0000000..e8f67a7
--- /dev/null
+++ b/home/terminal/termite/default.nix
@@ -0,0 +1,53 @@
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.my.home.terminal;
+in
+{
+ config = lib.mkIf (cfg.program == "termite") {
+ programs.termite = {
+ enable = true;
+
+ # Niceties
+ browser = "${pkgs.xdg-utils}/bin/xdg-open";
+ clickableUrl = true;
+ dynamicTitle = true;
+ fullscreen = false;
+ mouseAutohide = true;
+ urgentOnBell = true;
+
+ # Look and feel
+ allowBold = true;
+ audibleBell = false;
+ cursorBlink = "system";
+ font = "Monospace 9";
+ scrollbar = "off";
+
+
+ # Colors
+ backgroundColor = cfg.colors.background;
+ cursorColor = cfg.colors.cursor;
+ foregroundColor = cfg.colors.foreground;
+ foregroundBoldColor = cfg.colors.foregroundBold;
+ colorsExtra = with cfg.colors; ''
+ # Normal colors
+ color0 = ${black}
+ color1 = ${red}
+ color2 = ${green}
+ color3 = ${yellow}
+ color4 = ${blue}
+ color5 = ${magenta}
+ color6 = ${cyan}
+ color7 = ${white}
+ # Bold colors
+ color8 = ${blackBold}
+ color9 = ${redBold}
+ color10 = ${greenBold}
+ color11 = ${yellowBold}
+ color12 = ${blueBold}
+ color13 = ${magentaBold}
+ color14 = ${cyanBold}
+ color15 = ${whiteBold}
+ '';
+ };
+ };
+}
diff --git a/home/tmux.nix b/home/tmux.nix
deleted file mode 100644
index 6b05414..0000000
--- a/home/tmux.nix
+++ /dev/null
@@ -1,47 +0,0 @@
-{ config, lib, pkgs, ... }:
-let
- cfg = config.my.home.tmux;
-in
-{
- options.my.home.tmux = with lib.my; {
- enable = mkDisableOption "tmux terminal multiplexer";
- };
-
- config.programs.tmux = lib.mkIf cfg.enable {
- enable = true;
-
- keyMode = "vi"; # Home-row keys and other niceties
- clock24 = true; # I'm one of those heathens
- escapeTime = 0; # Let vim do its thing instead
- historyLimit = 5000; # Bigger buffer
- terminal = "tmux-256color"; # I want accurate termcap info
-
- plugins = with pkgs.tmuxPlugins; [
- # Open high-lighted files in copy mode
- open
- # Better pane management
- pain-control
- # Better session management
- sessionist
- # X clipboard integration
- yank
- {
- # Show when prefix has been pressed
- plugin = prefix-highlight;
- extraConfig = ''
- # Also show when I'm in copy or sync mode
- set -g @prefix_highlight_show_copy_mode 'on'
- set -g @prefix_highlight_show_sync_mode 'on'
- # Show prefix mode in status bar
- set -g status-right '#{prefix_highlight} %a %Y-%m-%d %H:%M'
- '';
- }
- ];
-
- extraConfig = ''
- # Better vim mode
- bind-key -T copy-mode-vi 'v' send -X begin-selection
- bind-key -T copy-mode-vi 'y' send -X copy-selection-and-cancel
- '';
- };
-}
diff --git a/home/tmux/default.nix b/home/tmux/default.nix
new file mode 100644
index 0000000..70f037f
--- /dev/null
+++ b/home/tmux/default.nix
@@ -0,0 +1,80 @@
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.my.home.tmux;
+ hasGUI = lib.any lib.id [
+ config.my.home.x.enable
+ (config.my.home.wm.windowManager != null)
+ ];
+in
+{
+ options.my.home.tmux = with lib; {
+ enable = my.mkDisableOption "tmux terminal multiplexer";
+
+ enabledPassthrough = mkEnableOption "tmux DCS passthrough sequence";
+ };
+
+ config.programs.tmux = lib.mkIf cfg.enable {
+ enable = true;
+
+ keyMode = "vi"; # Home-row keys and other niceties
+ clock24 = true; # I'm one of those heathens
+ escapeTime = 0; # Let vim do its thing instead
+ historyLimit = 50000; # Bigger buffer
+ terminal = "tmux-256color"; # I want accurate termcap info
+
+ plugins = with pkgs.tmuxPlugins; [
+ # Open high-lighted files in copy mode
+ open
+ # Better pane management
+ pain-control
+ # Better session management
+ sessionist
+ {
+ # X clipboard integration
+ plugin = yank;
+ extraConfig = ''
+ # Use 'clipboard' because of misbehaving apps (e.g: firefox)
+ set -g @yank_selection_mouse 'clipboard'
+ # Stay in copy mode after yanking
+ set -g @yank_action 'copy-pipe'
+ '';
+ }
+ {
+ # Show when prefix has been pressed
+ plugin = prefix-highlight;
+ extraConfig = ''
+ # Also show when I'm in copy or sync mode
+ set -g @prefix_highlight_show_copy_mode 'on'
+ set -g @prefix_highlight_show_sync_mode 'on'
+ # Show prefix mode in status bar
+ set -g status-right '#{prefix_highlight} %a %Y-%m-%d %H:%M'
+ '';
+ }
+ ];
+
+ extraConfig = ''
+ # Better vim mode
+ bind-key -T copy-mode-vi 'v' send -X begin-selection
+ ${
+ lib.optionalString
+ (!hasGUI)
+ "bind-key -T copy-mode-vi 'y' send -X copy-selection"
+ }
+ # Block selection in vim mode
+ bind-key -Tcopy-mode-vi 'C-v' send -X begin-selection \; send -X rectangle-toggle
+
+ # Allow any application to send OSC52 escapes to set the clipboard
+ set -s set-clipboard on
+
+ # Longer session names in status bar
+ set -g status-left-length 16
+
+ ${
+ lib.optionalString cfg.enabledPassthrough ''
+ # Allow any application to use the tmux DCS for passthrough
+ set -g allow-passthrough on
+ ''
+ }
+ '';
+ };
+}
diff --git a/home/udiskie/default.nix b/home/udiskie/default.nix
new file mode 100644
index 0000000..1f2119e
--- /dev/null
+++ b/home/udiskie/default.nix
@@ -0,0 +1,13 @@
+{ config, lib, ... }:
+let
+ cfg = config.my.home.udiskie;
+in
+{
+ options.my.home.udiskie = with lib; {
+ enable = mkEnableOption "udiskie configuration";
+ };
+
+ config.services.udiskie = lib.mkIf cfg.enable {
+ enable = true;
+ };
+}
diff --git a/home/vim/after/ftplugin/bash.vim b/home/vim/after/ftplugin/bash.vim
deleted file mode 100644
index 2b69ab4..0000000
--- a/home/vim/after/ftplugin/bash.vim
+++ /dev/null
@@ -1,14 +0,0 @@
-" Create the `b:undo_ftplugin` variable if it doesn't exist
-call ftplugined#check_undo_ft()
-
-" Use shfmt as ALE fixer for bash
-let b:ale_fixers=[ 'shfmt' ]
-let b:undo_ftplugin.='|unlet! b:ale_fixers'
-
-" Indent with 4 spaces, simplify script, indent switch cases, use Bash variant
-let b:ale_sh_shfmt_options='-i 4 -s -ci -ln bash'
-let b:undo_ftplugin.='|unlet! b:ale_sh_shfmt_options'
-
-" Use bash dialect explicitly, require explicit empty string test
-let b:ale_sh_shellcheck_options='-s bash -o avoid-nullary-conditions'
-let b:undo_ftplugin.='|unlet! b:ale_sh_shellcheck_options'
diff --git a/home/vim/after/ftplugin/beancount.vim b/home/vim/after/ftplugin/beancount.vim
index c5645c8..a2b142e 100644
--- a/home/vim/after/ftplugin/beancount.vim
+++ b/home/vim/after/ftplugin/beancount.vim
@@ -7,7 +7,3 @@ let b:undo_ftplugin.='|setlocal shiftwidth<'
" Have automatic padding of transactions so that decimal is on 52nd column
let g:beancount_separator_col=52
-
-" Automatic padding for transactions
-nnoremap = :AlignCommodity
-vnoremap = :AlignCommodity
diff --git a/home/vim/after/ftplugin/c.vim b/home/vim/after/ftplugin/c.vim
deleted file mode 100644
index a85ff07..0000000
--- a/home/vim/after/ftplugin/c.vim
+++ /dev/null
@@ -1,14 +0,0 @@
-" Create the `b:undo_ftplugin` variable if it doesn't exist
-call ftplugined#check_undo_ft()
-
-" More warnings and the usual version in flags for Clang
-let b:ale_c_clang_options='-Wall -Wextra -pedantic -std=c99'
-let b:undo_ftplugin.='|unlet! b:ale_c_clang_options'
-
-" More warnings and the usual version in flags for GCC
-let b:ale_c_gcc_options='-Wall -Wextra -pedantic -std=c99'
-let b:undo_ftplugin.='|unlet! b:ale_c_gcc_options'
-
-" Use compile_commands.json to look for additional flags
-let b:ale_c_parse_compile_commands=1
-let b:undo_ftplugin.='|unlet! b:ale_c_parse_compile_commands'
diff --git a/home/vim/after/ftplugin/cpp.vim b/home/vim/after/ftplugin/cpp.vim
deleted file mode 100644
index 4fa501e..0000000
--- a/home/vim/after/ftplugin/cpp.vim
+++ /dev/null
@@ -1,14 +0,0 @@
-" Create the `b:undo_ftplugin` variable if it doesn't exist
-call ftplugined#check_undo_ft()
-
-" More warnings and the usual version in flags for Clang
-let b:ale_cpp_clang_options='-Wall -Wextra -pedantic -std=c++17'
-let b:undo_ftplugin.='|unlet! b:ale_cpp_clang_options'
-
-" More warnings and the usual version in flags for GCC
-let b:ale_cpp_gcc_options='-Wall -Wextra -pedantic -std=c++17'
-let b:undo_ftplugin.='|unlet! b:ale_cpp_gcc_options'
-
-" Use compile_commands.json to look for additional flags
-let b:ale_c_parse_compile_commands=1
-let b:undo_ftplugin.='|unlet! b:ale_c_parse_compile_commands'
diff --git a/home/vim/after/ftplugin/d.vim b/home/vim/after/ftplugin/d.vim
deleted file mode 100644
index 0e868c7..0000000
--- a/home/vim/after/ftplugin/d.vim
+++ /dev/null
@@ -1,6 +0,0 @@
-" Create the `b:undo_ftplugin` variable if it doesn't exist
-call ftplugined#check_undo_ft()
-
-" Use my desired ALE fixer for D
-let b:ale_fixers=[ 'dfmt' ]
-let b:undo_ftplugin.='|unlet! b:ale_fixers'
diff --git a/home/vim/after/ftplugin/gitsendemail.vim b/home/vim/after/ftplugin/gitsendemail.vim
deleted file mode 100644
index fc9c729..0000000
--- a/home/vim/after/ftplugin/gitsendemail.vim
+++ /dev/null
@@ -1,2 +0,0 @@
-" Create the `b:undo_ftplugin` variable if it doesn't exist
-call ftplugined#check_undo_ft()
diff --git a/home/vim/after/ftplugin/haskell.vim b/home/vim/after/ftplugin/haskell.vim
index 80d7f2e..978f346 100644
--- a/home/vim/after/ftplugin/haskell.vim
+++ b/home/vim/after/ftplugin/haskell.vim
@@ -5,26 +5,6 @@ call ftplugined#check_undo_ft()
setlocal shiftwidth=2
let b:undo_ftplugin.='|setlocal shiftwidth<'
-" Use my desired ALE fixers for Haskell
-let b:ale_fixers=[ 'brittany' ]
-let b:undo_ftplugin.='|unlet! b:ale_fixers'
-
-" Use stack-managed `hlint`
-let b:ale_haskell_hlint_executable='stack'
-let b:undo_ftplugin.='|unlet! b:ale_haskell_hlint_executable'
-
-" Use stack-managed `brittany`
-let b:ale_haskell_brittany_executable='stack'
-let b:undo_ftplugin.='|unlet! b:ale_haskell_brittany_executable'
-
-" Use dynamic libraries because of Arch linux, with default ALE options
-let b:ale_haskell_ghc_options='--dynamic -fno-code -v0'
-let b:undo_ftplugin.='|unlet! b:ale_haskell_ghc_options'
-
-" Automatically format files when saving them
-let b:ale_fix_on_save=1
-let b:undo_ftplugin='|unlet! b:ale_lint_on_save'
-
" Change max length of a line to 100 for this buffer to match official guidelines
setlocal colorcolumn=100
let b:undo_ftplugin.='|setlocal colorcolumn<'
diff --git a/home/vim/after/ftplugin/json.vim b/home/vim/after/ftplugin/json.vim
deleted file mode 100644
index 2b4ba56..0000000
--- a/home/vim/after/ftplugin/json.vim
+++ /dev/null
@@ -1,6 +0,0 @@
-" Create the `b:undo_ftplugin` variable if it doesn't exist
-call ftplugined#check_undo_ft()
-
-" Use my desired ALE fixer for JSON
-let b:ale_fixers=[ 'jq' ]
-let b:undo_ftplugin.='|unlet! b:ale_fixers'
diff --git a/home/vim/after/ftplugin/pandoc.vim b/home/vim/after/ftplugin/markdown.vim
similarity index 68%
rename from home/vim/after/ftplugin/pandoc.vim
rename to home/vim/after/ftplugin/markdown.vim
index d44bc12..5b58b41 100644
--- a/home/vim/after/ftplugin/pandoc.vim
+++ b/home/vim/after/ftplugin/markdown.vim
@@ -1,10 +1,6 @@
" Create the `b:undo_ftplugin` variable if it doesn't exist
call ftplugined#check_undo_ft()
-" Let ALE know that I want Markdown linters
-let b:ale_linter_aliases=[ 'markdown' ]
-let b:undo_ftplugin.='|unlet! b:ale_linter_aliases'
-
" Use a small indentation value on Pandoc files
setlocal shiftwidth=2
let b:undo_ftplugin.='|setlocal shiftwidth<'
diff --git a/home/vim/after/ftplugin/nix.vim b/home/vim/after/ftplugin/nix.vim
new file mode 100644
index 0000000..fb6126e
--- /dev/null
+++ b/home/vim/after/ftplugin/nix.vim
@@ -0,0 +1,6 @@
+" Create the `b:undo_ftplugin` variable if it doesn't exist
+call ftplugined#check_undo_ft()
+
+" Use a small indentation value on Nix files
+setlocal shiftwidth=2
+let b:undo_ftplugin.='|setlocal shiftwidth<'
diff --git a/home/vim/after/ftplugin/python.vim b/home/vim/after/ftplugin/python.vim
deleted file mode 100644
index a18299e..0000000
--- a/home/vim/after/ftplugin/python.vim
+++ /dev/null
@@ -1,40 +0,0 @@
-" Create the `b:undo_ftplugin` variable if it doesn't exist
-call ftplugined#check_undo_ft()
-
-" Use my desired ALE fixers for python
-let b:ale_fixers=[ 'black', 'isort' ]
-let b:undo_ftplugin.='|unlet! b:ale_fixers'
-" Use my desired ALE linters for python
-let b:ale_linters=[ 'flake8', 'mypy', 'pylint', 'pyls' ]
-let b:undo_ftplugin.='|unlet! b:ale_linters'
-
-" Use pyls inside the python environment if needed
-let b:ale_python_pyls_auto_pipenv=1
-let b:undo_ftplugin.='|unlet! b:ale_python_pyls_auto_pipenv'
-
-" Disable pycodestyle checks from pyls because I'm already using flake8
-let b:ale_python_pyls_config={
- \ 'pyls': {
- \ 'plugins': {
- \ 'pycodestyle': {
- \ 'enabled': v:false
- \ },
- \ },
- \ },
- \ }
-let b:undo_ftplugin.='|unlet! b:ale_python_pyls_config'
-
-" Don't use mypy to check for syntax errors
-let b:ale_python_mypy_ignore_invalid_syntax=1
-let b:undo_ftplugin.='|unlet! b:ale_python_mypy_ignore_invalid_syntax'
-" Use mypy inside the python environment if needed
-let b:ale_python_mypy_auto_pipenv=1
-let b:undo_ftplugin.='|unlet! b:ale_python_mypy_auto_pipenv'
-
-" Automatically format files when saving them
-let b:ale_fix_on_save=1
-let b:undo_ftplugin='|unlet! b:ale_lint_on_save'
-
-" Change max length of a line to 88 for this buffer to match black's settings
-setlocal colorcolumn=88
-let b:undo_ftplugin.='|setlocal colorcolumn<'
diff --git a/home/vim/after/ftplugin/qf.vim b/home/vim/after/ftplugin/qf.vim
deleted file mode 100644
index 01036f9..0000000
--- a/home/vim/after/ftplugin/qf.vim
+++ /dev/null
@@ -1,8 +0,0 @@
-" Create the `b:undo_ftplugin` variable if it doesn't exist
-call ftplugined#check_undo_ft()
-
-" Use h/l to go to the previous/next non-empty quickfix or location list
-nnoremap h :call quickfixed#older()
-let b:undo_ftplugin.='|nunmap h'
-nnoremap l :call quickfixed#newer()
-let b:undo_ftplugin.='|nunmap l'
diff --git a/home/vim/after/ftplugin/rust.vim b/home/vim/after/ftplugin/rust.vim
index 61516f9..8738a54 100644
--- a/home/vim/after/ftplugin/rust.vim
+++ b/home/vim/after/ftplugin/rust.vim
@@ -1,31 +1,6 @@
" Create the `b:undo_ftplugin` variable if it doesn't exist
call ftplugined#check_undo_ft()
-" Check tests too
-let b:ale_rust_cargo_check_tests=1
-let b:undo_ftplugin='|unlet! b:ale_rust_cargo_check_tests'
-
-" Check examples too
-let b:ale_rust_cargo_check_examples=1
-let b:undo_ftplugin='|unlet! b:ale_rust_cargo_check_examples'
-
-" Use clippy if it's available instead of just cargo check
-let b:ale_rust_cargo_use_clippy=executable('cargo-clippy')
-let b:undo_ftplugin='|unlet! b:ale_rust_cargo_use_clippy'
-
-" Use rust-analyzer instead of RLS as a linter
-let b:ale_linters=[ 'cargo', 'analyzer' ]
-let b:undo_ftplugin='|unlet! b:ale_linters'
-
-
-" Use rustfmt as ALE fixer for rust
-let b:ale_fixers=[ 'rustfmt' ]
-let b:undo_ftplugin.='|unlet! b:ale_fixers'
-
-" Automatically format files when saving them
-let b:ale_fix_on_save=1
-let b:undo_ftplugin='|unlet! b:ale_lint_on_save'
-
" Change max length of a line to 99 for this buffer to match official guidelines
setlocal colorcolumn=99
let b:undo_ftplugin.='|setlocal colorcolumn<'
diff --git a/home/vim/after/ftplugin/sh.vim b/home/vim/after/ftplugin/sh.vim
deleted file mode 100644
index 5b5a88c..0000000
--- a/home/vim/after/ftplugin/sh.vim
+++ /dev/null
@@ -1,14 +0,0 @@
-" Create the `b:undo_ftplugin` variable if it doesn't exist
-call ftplugined#check_undo_ft()
-
-" Use shfmt as ALE fixer for sh
-let b:ale_fixers=[ 'shfmt' ]
-let b:undo_ftplugin.='|unlet! b:ale_fixers'
-
-" Indent with 4 spaces, simplify the code, indent switch cases, use POSIX
-let b:ale_sh_shfmt_options='-i 4 -s -ci -ln posix'
-let b:undo_ftplugin.='|unlet! b:ale_sh_shfmt_options'
-
-" Require explicit empty string test
-let b:ale_sh_shellcheck_options='-o avoid-nullary-conditions'
-let b:undo_ftplugin.='|unlet! b:ale_sh_shellcheck_options'
diff --git a/home/vim/after/ftplugin/tiger.vim b/home/vim/after/ftplugin/tiger.vim
new file mode 100644
index 0000000..81c2cfc
--- /dev/null
+++ b/home/vim/after/ftplugin/tiger.vim
@@ -0,0 +1,6 @@
+" Create the `b:undo_ftplugin` variable if it doesn't exist
+call ftplugined#check_undo_ft()
+
+" Use a small indentation value on tiger files
+setlocal shiftwidth=2
+let b:undo_ftplugin.='|setlocal shiftwidth<'
diff --git a/home/vim/after/ftplugin/zsh.vim b/home/vim/after/ftplugin/zsh.vim
deleted file mode 100644
index b8c7a31..0000000
--- a/home/vim/after/ftplugin/zsh.vim
+++ /dev/null
@@ -1,14 +0,0 @@
-" Create the `b:undo_ftplugin` variable if it doesn't exist
-call ftplugined#check_undo_ft()
-
-" Use shfmt as ALE fixer for zsh
-let b:ale_fixers=[ 'shfmt' ]
-let b:undo_ftplugin.='|unlet! b:ale_fixers'
-
-" Indent with 4 spaces, simplify script, indent switch cases, use Bash variant
-let b:ale_sh_shfmt_options='-i 4 -s -ci -ln bash'
-let b:undo_ftplugin.='|unlet! b:ale_sh_shfmt_options'
-
-" Use bash dialect explicitly, require explicit empty string test
-let b:ale_sh_shellcheck_options='-s bash -o avoid-nullary-conditions'
-let b:undo_ftplugin.='|unlet! b:ale_sh_shellcheck_options'
diff --git a/home/vim/after/plugin/mappings/ale.vim b/home/vim/after/plugin/mappings/ale.vim
deleted file mode 100644
index 3069f81..0000000
--- a/home/vim/after/plugin/mappings/ale.vim
+++ /dev/null
@@ -1,2 +0,0 @@
-" Use ALE LSP-powered symbol information
-nnoremap K :ALEHover
diff --git a/home/vim/after/plugin/mappings/commentary.lua b/home/vim/after/plugin/mappings/commentary.lua
new file mode 100644
index 0000000..6ed3b89
--- /dev/null
+++ b/home/vim/after/plugin/mappings/commentary.lua
@@ -0,0 +1,10 @@
+local wk = require("which-key")
+
+local keys = {
+ name = "Comment/uncomment",
+ c = "Current line",
+ u = "Uncomment the current and adjacent commented lines",
+ ["gc"] = "Uncomment the current and adjacent commented lines",
+}
+
+wk.register(keys, { prefix = "gc" })
diff --git a/home/vim/after/plugin/mappings/fugitive.vim b/home/vim/after/plugin/mappings/fugitive.vim
deleted file mode 100644
index 934f00e..0000000
--- a/home/vim/after/plugin/mappings/fugitive.vim
+++ /dev/null
@@ -1,10 +0,0 @@
-" Visual bindings for merging diffs as in normal mode
-xnoremap dp :diffput
-xnoremap do :diffget
-
-" Open status window
-nnoremap gs :Gstatus
-" Open diff view of current buffer: the up/left window is the current index
-nnoremap gd :Gdiffsplit!
-" Open current file log in new tab, populate its location list with history
-nnoremap gl :spT:Gllog --follow -- %:p
diff --git a/home/vim/after/plugin/mappings/fzf.vim b/home/vim/after/plugin/mappings/fzf.vim
deleted file mode 100644
index 8cfb8b8..0000000
--- a/home/vim/after/plugin/mappings/fzf.vim
+++ /dev/null
@@ -1,38 +0,0 @@
-" Only git-tracked files, Vim needs to be in a Git repository
-nnoremap fg :GFiles
-
-" All files
-nnoremap ff :Files
-
-" Currently open buffers
-nnoremap fb :Buffers
-
-" Buffer history
-nnoremap fh :History
-
-" Tags in buffer
-nnoremap ft :BTags
-
-" Tags in all project files
-nnoremap fT :Tags
-
-" Snippets for the current fileytpe (using Ultisnips)
-nnoremap fs :Snippets
-
-" All available commands
-nnoremap f: :Commands
-
-" All commits (using fugitive)
-nnoremap fc :Commits
-
-" All commits for the current buffer (using fugitive)
-nnoremap fC :BCommits
-
-" Select normal mode mapping by searching for its name
-nmap (fzf-maps-n)
-
-" Select visual mode mapping by searching for its name
-xmap (fzf-maps-x)
-
-" Select operator pending mode mapping by searching for its name
-omap (fzf-maps-o)
diff --git a/home/vim/after/plugin/mappings/misc.lua b/home/vim/after/plugin/mappings/misc.lua
new file mode 100644
index 0000000..6aa25a2
--- /dev/null
+++ b/home/vim/after/plugin/mappings/misc.lua
@@ -0,0 +1,7 @@
+local wk = require("which-key")
+
+local keys = {
+ [""] = { "nohls", "Clear search highlight" },
+}
+
+wk.register(keys, { prefix = "" })
diff --git a/home/vim/after/plugin/mappings/misc.vim b/home/vim/after/plugin/mappings/misc.vim
deleted file mode 100644
index 7f1ea1c..0000000
--- a/home/vim/after/plugin/mappings/misc.vim
+++ /dev/null
@@ -1,8 +0,0 @@
-" Yank until the end of line with Y, to be more consistent with D and C
-nnoremap Y y$
-
-" Run make silently, then skip the 'Press ENTER to continue'
-noremap m :silent! :make! \| :redraw!
-
-" Remove search-highlighting
-noremap :nohls
diff --git a/home/vim/after/plugin/mappings/qf.vim b/home/vim/after/plugin/mappings/qf.vim
deleted file mode 100644
index c5e353b..0000000
--- a/home/vim/after/plugin/mappings/qf.vim
+++ /dev/null
@@ -1,11 +0,0 @@
-" Next and previous in quick-fix list
-nmap fn (qf_qf_next)
-nmap fp (qf_qf_previous)
-
-" Next and previous in location list
-nmap ln (qf_loc_next)
-nmap lp (qf_loc_previous)
-
-" Toggle quick-fix and location lists
-nmap tf (qf_qf_toggle)
-nmap tl (qf_loc_toggle)
diff --git a/home/vim/after/plugin/mappings/telescope.lua b/home/vim/after/plugin/mappings/telescope.lua
new file mode 100644
index 0000000..e0d38a7
--- /dev/null
+++ b/home/vim/after/plugin/mappings/telescope.lua
@@ -0,0 +1,17 @@
+local wk = require("which-key")
+local telescope = require("telescope")
+local telescope_builtin = require("telescope.builtin")
+
+local keys = {
+ f = {
+ name = "Fuzzy finder",
+ b = { telescope_builtin.buffers, "Open buffers" },
+ f = { telescope_builtin.git_files, "Git tracked files" },
+ F = { telescope_builtin.find_files, "Files" },
+ g = { telescope_builtin.live_grep, "Grep string" },
+ G = { telescope_builtin.grep_string, "Grep string under cursor" },
+ n = { telescope.extensions.notify.notify, "Notification history" },
+ },
+}
+
+wk.register(keys, { prefix = "" })
diff --git a/home/vim/after/plugin/mappings/tree-sitter-textobjects.lua b/home/vim/after/plugin/mappings/tree-sitter-textobjects.lua
new file mode 100644
index 0000000..631731c
--- /dev/null
+++ b/home/vim/after/plugin/mappings/tree-sitter-textobjects.lua
@@ -0,0 +1,30 @@
+local wk = require("which-key")
+
+local motions = {
+ ["]m"] = "Next method start",
+ ["]M"] = "Next method end",
+ ["]S"] = "Next statement start",
+ ["]]"] = "Next class start",
+ ["]["] = "Next class end",
+ ["[m"] = "Previous method start",
+ ["[M"] = "Previous method end",
+ ["[S"] = "Previous statement start",
+ ["[["] = "Previous class start",
+ ["[]"] = "Previous class end",
+}
+
+local objects = {
+ ["aa"] = "a parameter",
+ ["ia"] = "inner parameter",
+ ["ab"] = "a block",
+ ["ib"] = "inner block",
+ ["ac"] = "a class",
+ ["ic"] = "inner class",
+ ["af"] = "a function",
+ ["if"] = "inner function",
+ ["ak"] = "a comment",
+ ["aS"] = "a statement",
+}
+
+wk.register(motions, { mode = "n" })
+wk.register(objects, { mode = "o" })
diff --git a/home/vim/after/plugin/mappings/unimpaired.lua b/home/vim/after/plugin/mappings/unimpaired.lua
new file mode 100644
index 0000000..f502056
--- /dev/null
+++ b/home/vim/after/plugin/mappings/unimpaired.lua
@@ -0,0 +1,128 @@
+local wk = require("which-key")
+
+local lsp = require("ambroisie.lsp")
+
+local keys = {
+ -- Edition and navigation mappins
+ ["["] = {
+ name = "Previous",
+ [""] = "Insert blank line above",
+ [""] = "Previous location list file",
+ [""] = "Previous quickfix list file",
+ [""] = "Previous tag in preview window",
+ a = "Previous argument",
+ A = "First argument",
+ b = "Previous buffer",
+ B = "First buffer",
+ e = "Exchange previous line",
+ f = "Previous file in directory",
+ l = "Previous location list entry",
+ L = "First Location list entry",
+ n = "Previous conflict marker/diff hunk",
+ p = "Paste line above",
+ P = "Paste line above",
+ q = "Previous quickfix list entry",
+ Q = "First quickfix list entry",
+ t = "Previous matching tag",
+ T = "First matching tag",
+ z = "Previous fold",
+ -- Encoding
+ C = "C string encode",
+ u = "URL encode",
+ x = "XML encode",
+ y = "C string encode",
+ -- Custom
+ d = { lsp.goto_prev_diagnostic, "Previous diagnostic" },
+ },
+ ["]"] = {
+ name = "Next",
+ [""] = "Insert blank line below",
+ [""] = "Next location list file",
+ [""] = "Next quickfix list file",
+ [""] = "Next tag in preview window",
+ a = "Next argument",
+ A = "Last argument",
+ b = "Next buffer",
+ B = "Last buffer",
+ e = "Exchange next line",
+ f = "Next file in directory",
+ l = "Next location list entry",
+ L = "Last Location list entry",
+ n = "Next conflict marker/diff hunk",
+ p = "Paste line below",
+ P = "Paste line below",
+ q = "Next quickfix list entry",
+ Q = "Last quickfix list entry",
+ t = "Next matching tag",
+ T = "Last matching tag",
+ z = "Next fold",
+ -- Decoding
+ C = "C string decode",
+ u = "URL decode",
+ x = "XML decode",
+ y = "C string decode",
+ -- Custom
+ d = { lsp.goto_next_diagnostic, "Next diagnostic" },
+ },
+
+ -- Option mappings
+ ["[o"] = {
+ name = "Enable option",
+ b = "Light background",
+ c = "Cursor line",
+ d = "Diff",
+ f = { "FormatEnable", "LSP Formatting" },
+ h = "Search high-lighting",
+ i = "Case insensitive search",
+ l = "List mode",
+ n = "Line numbers",
+ r = "Relative line numbers",
+ p = { "lwindow", "Location list" },
+ q = { "cwindow", "Quickfix list" },
+ u = "Cursor column",
+ v = "Virtual editing",
+ w = "Text wrapping",
+ x = "Cursor line and column",
+ z = "Spell checking",
+ },
+ ["]o"] = {
+ name = "Option off",
+ b = "Light background",
+ c = "Cursor line",
+ d = "Diff",
+ f = { "FormatDisable", "LSP Formatting" },
+ h = "Search high-lighting",
+ i = "Case insensitive search",
+ l = "List mode",
+ n = "Line numbers",
+ p = { "lclose", "Location list" },
+ q = { "cclose", "Quickfix list" },
+ r = "Relative line numbers",
+ u = "Cursor column",
+ v = "Virtual editing",
+ w = "Text wrapping",
+ x = "Cursor line and column",
+ z = "Spell checking",
+ },
+ ["yo"] = {
+ name = "Option toggle",
+ b = "Light background",
+ c = "Cursor line",
+ d = "Diff",
+ f = { "FormatToggle", "LSP Formatting" },
+ h = "Search high-lighting",
+ i = "Case insensitive search",
+ l = "List mode",
+ n = "Line numbers",
+ p = { "(qf_loc_toggle)", "Location list" },
+ q = { "(qf_qf_toggle)", "Quickfix list" },
+ r = "Relative line numbers",
+ u = "Cursor column",
+ v = "Virtual editing",
+ w = "Text wrapping",
+ x = "Cursor line and column",
+ z = "Spell checking",
+ },
+}
+
+wk.register(keys)
diff --git a/home/vim/after/plugin/mappings/unimpaired.vim b/home/vim/after/plugin/mappings/unimpaired.vim
deleted file mode 100644
index 53457bd..0000000
--- a/home/vim/after/plugin/mappings/unimpaired.vim
+++ /dev/null
@@ -1,7 +0,0 @@
-" Better fr layout mappings for vim-unimpaired and other '[' and ']' commands
-nmap ( [
-nmap ) ]
-omap ( [
-omap ) ]
-xmap ( [
-xmap ) ]
diff --git a/home/vim/autoload/quickfixed.vim b/home/vim/autoload/quickfixed.vim
deleted file mode 100644
index 4862f8f..0000000
--- a/home/vim/autoload/quickfixed.vim
+++ /dev/null
@@ -1,68 +0,0 @@
-" Taken from the Vimways article
-
-function! s:isLocation()
- " Get dictionary of properties of the current window
- let wininfo = filter(getwininfo(), {i,v -> v.winnr == winnr()})[0]
- return wininfo.loclist
-endfunction
-
-function! s:length()
- " Get the size of the current quickfix/location list
- return len(s:isLocation() ? getloclist(0) : getqflist())
-endfunction
-
-function! s:getProperty(key, ...)
- " getqflist() and getloclist() expect a dictionary argument
- " If a 2nd argument has been passed in, use it as the value, else 0
- let l:what = {a:key : a:0 ? a:1 : 0}
- let l:listdict = s:isLocation() ? getloclist(0, l:what) : getqflist(l:what)
- return get(l:listdict, a:key)
-endfunction
-
-function! s:isFirst()
- return s:getProperty('nr') <= 1
-endfunction
-
-function! s:isLast()
- return s:getProperty('nr') == s:getProperty('nr', '$')
-endfunction
-
-function! s:history(goNewer)
- " Build the command: one of colder/cnewer/lolder/lnewer
- let l:cmd = (s:isLocation() ? 'l' : 'c') . (a:goNewer ? 'newer' : 'older')
-
- " Apply the cmd repeatedly until we hit a non-empty list, or first/last list
- " is reached
- while 1
- if (a:goNewer && s:isLast()) || (!a:goNewer && s:isFirst()) | break | endif
- " Run the command. Use :silent to suppress message-history output.
- " Note that the :try wrapper is no longer necessary
- silent execute l:cmd
- if s:length() | break | endif
- endwhile
-
- " Echo a description of the new quickfix / location list.
- " And make it look like a rainbow.
- let l:nr = s:getProperty('nr')
- let l:last = s:getProperty('nr', '$')
- echohl MoreMsg | echon '('
- echohl Identifier | echon l:nr
- if l:last > 1
- echohl LineNr | echon ' of '
- echohl Identifier | echon l:last
- endif
- echohl MoreMsg | echon ') '
- echohl MoreMsg | echon '['
- echohl Identifier | echon s:length()
- echohl MoreMsg | echon '] '
- echohl Normal | echon s:getProperty('title')
- echohl None
-endfunction
-
-function! quickfixed#older()
- call s:history(0)
-endfunction
-
-function! quickfixed#newer()
- call s:history(1)
-endfunction
diff --git a/home/vim/default.nix b/home/vim/default.nix
index 08f5ecd..3bd2469 100644
--- a/home/vim/default.nix
+++ b/home/vim/default.nix
@@ -13,16 +13,21 @@ let
"after"
"autoload"
"ftdetect"
+ "lua"
"plugin"
];
in
{
- options.my.home.vim = with lib.my; {
- enable = mkDisableOption "vim configuration";
+ options.my.home.vim = with lib; {
+ enable = my.mkDisableOption "vim configuration";
};
config.programs.neovim = lib.mkIf cfg.enable {
enable = true;
+
+ # This is the best editor
+ defaultEditor = true;
+
# All the aliases
viAlias = true;
vimAlias = true;
@@ -30,11 +35,9 @@ in
plugins = with pkgs.vimPlugins; [
# Theming
- lightline-vim # Fancy status bar
- {
- plugin = onedark-vim; # Nice dark theme
- optional = true; # Needs to be `packadd`-ed manually...
- }
+ gruvbox-nvim # Nice dark theme
+ lualine-nvim # A lua-based status line
+ lualine-lsp-progress # Show progress for LSP servers
# tpope essentials
vim-commentary # Easy comments
@@ -43,7 +46,6 @@ in
vim-git # Sane git syntax files
vim-repeat # Enanche '.' for plugins
vim-rsi # Readline mappings
- vim-surround # Deal with pairs
vim-unimpaired # Some ex command mappings
vim-vinegar # Better netrw
@@ -52,25 +54,61 @@ in
vim-beancount
vim-jsonnet
vim-nix
- vim-pandoc
- vim-pandoc-syntax
vim-toml
# General enhancements
- fastfold # Better folding
vim-qf # Better quick-fix list
+ nvim-osc52 # Send clipboard data through terminal escape for SSH
# Other wrappers
- fzfWrapper # The vim plugin inside the 'fzf' package
- fzf-vim # Fuzzy commands
git-messenger-vim # A simple blame window
# LSP and linting
- ale # Asynchronous Linting Engine
- lightline-ale # Status bar integration
+ nvim-lspconfig # Easy LSP configuration
+ lsp-format-nvim # Simplified formatting configuration
+ lsp_lines-nvim # Show diagnostics *over* regions
+ null-ls-nvim # LSP integration for linters and formatters
+ nvim-treesitter.withAllGrammars # Better highlighting
+ nvim-treesitter-textobjects # More textobjects
+ nvim-ts-context-commentstring # Comment string in nested language blocks
+ plenary-nvim # 'null-ls', 'telescope' dependency
+
+ # Completion
+ luasnip # Snippet manager compatible with LSP
+ friendly-snippets # LSP snippets collection
+ nvim-cmp # Completion engine
+ cmp-buffer # Words from open buffers
+ cmp-nvim-lsp # LSP suggestions
+ cmp-nvim-lua # NeoVim lua API
+ cmp-path # Path name suggestions
+ cmp-under-comparator # Sort items that start with '_' lower
+ cmp_luasnip # Snippet suggestions from LuaSnip
+
+ # UX improvements
+ dressing-nvim # Integrate native UI hooks with Telescope etc...
+ gitsigns-nvim # Fast git UI integration
+ nvim-notify # Better notification API
+ nvim-surround # Deal with pairs, now in Lua
+ telescope-fzf-native-nvim # Use 'fzf' fuzzy matching algorithm
+ telescope-lsp-handlers-nvim # Use 'telescope' for various LSP actions
+ telescope-nvim # Fuzzy finder interface
+ which-key-nvim # Show available mappings
];
extraConfig = builtins.readFile ./init.vim;
+
+ # Linters, formatters, etc...
+ extraPackages = with pkgs; [
+ # C/C++
+ clang-tools
+
+ # Nix
+ nixpkgs-fmt
+
+ # Shell
+ shellcheck
+ shfmt
+ ];
};
config.xdg.configFile = lib.mkIf cfg.enable configFiles;
diff --git a/home/vim/ftdetect/automake.lua b/home/vim/ftdetect/automake.lua
new file mode 100644
index 0000000..cfa15d2
--- /dev/null
+++ b/home/vim/ftdetect/automake.lua
@@ -0,0 +1,6 @@
+-- Use Automake filetype for `local.am` files, explicit `set` to force override
+vim.filetype.add({
+ filename = {
+ ["local.am"] = "automake",
+ },
+})
diff --git a/home/vim/ftdetect/automake.vim b/home/vim/ftdetect/automake.vim
deleted file mode 100644
index 5cc73b0..0000000
--- a/home/vim/ftdetect/automake.vim
+++ /dev/null
@@ -1,2 +0,0 @@
-" Use Automake filetype for `local.am` files, explicit `set` to force override
-au BufNewFile,BufRead local.am set filetype=automake
diff --git a/home/vim/ftdetect/direnv.lua b/home/vim/ftdetect/direnv.lua
new file mode 100644
index 0000000..fba9748
--- /dev/null
+++ b/home/vim/ftdetect/direnv.lua
@@ -0,0 +1,6 @@
+-- Use bash filetype for `.envrc` files
+vim.filetype.add({
+ filename = {
+ [".envrc"] = "bash",
+ },
+})
diff --git a/home/vim/ftdetect/kbuild.lua b/home/vim/ftdetect/kbuild.lua
new file mode 100644
index 0000000..799570e
--- /dev/null
+++ b/home/vim/ftdetect/kbuild.lua
@@ -0,0 +1,6 @@
+-- Kbuild is just a Makefile under a different name
+vim.filetype.add({
+ filename = {
+ ["Kbuild"] = "make",
+ },
+})
diff --git a/home/vim/ftdetect/kconfig.lua b/home/vim/ftdetect/kconfig.lua
new file mode 100644
index 0000000..d51e964
--- /dev/null
+++ b/home/vim/ftdetect/kconfig.lua
@@ -0,0 +1,6 @@
+-- Mconfig is just Kconfig
+vim.filetype.add({
+ filename = {
+ ["Mconfig"] = "kconfig",
+ },
+})
diff --git a/home/vim/ftdetect/tiger.lua b/home/vim/ftdetect/tiger.lua
new file mode 100644
index 0000000..a261103
--- /dev/null
+++ b/home/vim/ftdetect/tiger.lua
@@ -0,0 +1,7 @@
+-- Use Tiger filetype for programs and header files
+vim.filetype.add({
+ extension = {
+ tig = "tiger",
+ tih = "tiger",
+ },
+})
diff --git a/home/vim/ftdetect/tikz.lua b/home/vim/ftdetect/tikz.lua
new file mode 100644
index 0000000..93b7db0
--- /dev/null
+++ b/home/vim/ftdetect/tikz.lua
@@ -0,0 +1,6 @@
+-- Use LaTeX filetype for TikZ files
+vim.filetype.add({
+ extension = {
+ tikz = "tex",
+ },
+})
diff --git a/home/vim/ftdetect/tikz.vim b/home/vim/ftdetect/tikz.vim
deleted file mode 100644
index 7327b11..0000000
--- a/home/vim/ftdetect/tikz.vim
+++ /dev/null
@@ -1,2 +0,0 @@
-" Use LaTeX filetype for TikZ files
-au BufNewFile,BufRead *.tikz setfiletype tex
diff --git a/home/vim/init.vim b/home/vim/init.vim
index 8968204..bd63d25 100644
--- a/home/vim/init.vim
+++ b/home/vim/init.vim
@@ -13,6 +13,13 @@ set wildmenu
" Enable syntax high-lighting and file-type specific plugins
syntax on
filetype plugin indent on
+
+" Map leader to space (needs the noremap trick to avoid moving the cursor)
+nnoremap
+let mapleader=" "
+
+" Map localleader to '!' (if I want to filter text, I use visual mode)
+let maplocalleader="!"
" }}}
" Indentation configuration {{{
@@ -59,19 +66,30 @@ set list
" Show a tab as an arrow, trailing spaces as ¤, non-breaking spaces as dots
set listchars=tab:>─,trail:·,nbsp:¤
+" Use patience diff
+set diffopt+=algorithm:patience
+" Align similar lines in each hunk
+set diffopt+=linematch:50
+
" Don't redraw when executing macros
set lazyredraw
" Timeout quickly on shortcuts, I can't wait two seconds to delete in visual
set timeoutlen=500
+" Timeout quickly for CursorHold events (and also swap file)
+set updatetime=250
+
+" Disable all mouse integrations
+set mouse=
+
" Set dark mode by default
set background=dark
-" Load it manually because of autoload functions...
-packadd! onedark-vim
-" Use onedark
-colorscheme onedark
+" 24 bit colors
+set termguicolors
+" Use my preferred colorscheme
+colorscheme gruvbox
" }}}
" Search parameters {{{
@@ -84,12 +102,10 @@ set ignorecase
set smartcase
" }}}
-" Import settings when inside a git repository {{{
+" Project-local settings {{{
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
-let git_settings=system("git config --get vim.settings")
-if strlen(git_settings)
- exe "set" git_settings
-endif
+" Securely read `.nvim.lua` or `.nvimrc`.
+set exrc
" }}}
" vim: foldmethod=marker
diff --git a/home/vim/lua/ambroisie/lsp.lua b/home/vim/lua/ambroisie/lsp.lua
new file mode 100644
index 0000000..7ef6e26
--- /dev/null
+++ b/home/vim/lua/ambroisie/lsp.lua
@@ -0,0 +1,118 @@
+local M = {}
+
+-- Simplified LSP formatting configuration
+local lsp_format = require("lsp-format")
+
+-- Move to the next/previous diagnostic, automatically showing the diagnostics
+-- float if necessary.
+-- @param forward whether to go forward or backwards
+local function goto_diagnostic(forward)
+ vim.validate({
+ forward = { forward, "boolean" },
+ })
+
+ local opts = {
+ float = false,
+ }
+
+ -- Only show floating diagnostics if they are otherwise not displayed
+ local config = vim.diagnostic.config()
+ if not (config.virtual_text or config.virtual_lines) then
+ opts.float = true
+ end
+
+ if forward then
+ vim.diagnostic.goto_next(opts)
+ else
+ vim.diagnostic.goto_prev(opts)
+ end
+end
+
+-- Move to the next diagnostic, automatically showing the diagnostics float if
+-- necessary.
+M.goto_next_diagnostic = function()
+ goto_diagnostic(true)
+end
+
+-- Move to the previous diagnostic, automatically showing the diagnostics float
+-- if necessary.
+M.goto_prev_diagnostic = function()
+ goto_diagnostic(false)
+end
+
+-- shared LSP configuration callback
+-- @param client native client configuration
+-- @param bufnr int? buffer number of the attched client
+M.on_attach = function(client, bufnr)
+ -- Format on save
+ lsp_format.on_attach(client, bufnr)
+
+ -- Mappings
+ local wk = require("which-key")
+
+ local function list_workspace_folders()
+ local utils = require("ambroisie.utils")
+ utils.dump(vim.lsp.buf.list_workspace_folders())
+ end
+
+ local function cycle_diagnostics_display()
+ -- Cycle from:
+ -- * nothing displayed
+ -- * single diagnostic at the end of the line (`virtual_text`)
+ -- * full diagnostics using virtual text (`virtual_lines`)
+ local text = vim.diagnostic.config().virtual_text
+ local lines = vim.diagnostic.config().virtual_lines
+
+ -- Text -> Lines transition
+ if text then
+ text = false
+ lines = true
+ -- Lines -> Nothing transition
+ elseif lines then
+ text = false
+ lines = false
+ -- Nothing -> Text transition
+ else
+ text = true
+ lines = false
+ end
+
+ vim.diagnostic.config({
+ virtual_text = text,
+ virtual_lines = lines,
+ })
+ end
+
+ local function show_buffer_diagnostics()
+ vim.diagnostic.open_float(nil, { scope = "buffer" })
+ end
+
+ local keys = {
+ K = { vim.lsp.buf.hover, "Show symbol information" },
+ [""] = { vim.lsp.buf.signature_help, "Show signature information" },
+ ["gd"] = { vim.lsp.buf.definition, "Go to definition" },
+ ["gD"] = { vim.lsp.buf.declaration, "Go to declaration" },
+ ["gi"] = { vim.lsp.buf.implementation, "Go to implementation" },
+ ["gr"] = { vim.lsp.buf.references, "List all references" },
+
+ ["c"] = {
+ name = "Code",
+ a = { vim.lsp.buf.code_action, "Code actions" },
+ d = { cycle_diagnostics_display, "Cycle diagnostics display" },
+ D = { show_buffer_diagnostics, "Show buffer diagnostics" },
+ r = { vim.lsp.buf.rename, "Rename symbol" },
+ s = { vim.lsp.buf.signature_help, "Show signature" },
+ t = { vim.lsp.buf.type_definition, "Go to type definition" },
+ w = {
+ name = "Workspace",
+ a = { vim.lsp.buf.add_workspace_folder, "Add folder to workspace" },
+ l = { list_workspace_folders, "List folders in workspace" },
+ r = { vim.lsp.buf.remove_workspace_folder, "Remove folder from workspace" },
+ },
+ },
+ }
+
+ wk.register(keys, { buffer = bufnr })
+end
+
+return M
diff --git a/home/vim/lua/ambroisie/utils.lua b/home/vim/lua/ambroisie/utils.lua
new file mode 100644
index 0000000..984c730
--- /dev/null
+++ b/home/vim/lua/ambroisie/utils.lua
@@ -0,0 +1,57 @@
+local M = {}
+
+-- pretty print lua object
+-- @param obj any object to pretty print
+M.dump = function(obj)
+ print(vim.inspect(obj))
+end
+
+--- checks if a given command is executable
+---@param cmd string? command to check
+---@return boolean executable
+M.is_executable = function(cmd)
+ return cmd and vim.fn.executable(cmd) == 1
+end
+
+--- return a function that checks if a given command is executable
+---@param cmd string? command to check
+---@return fun(cmd: string): boolean executable
+M.is_executable_condition = function(cmd)
+ return function()
+ return M.is_executable(cmd)
+ end
+end
+
+-- whether or not we are currently in an SSH connection
+-- @return boolean ssh connection
+M.is_ssh = function()
+ local variables = {
+ "SSH_CONNECTION",
+ "SSH_CLIENT",
+ "SSH_TTY",
+ }
+
+ for _, var in ipairs(variables) do
+ if string.len(os.getenv(var) or "") ~= 0 then
+ return true
+ end
+ end
+
+ return false
+end
+
+-- list all active LSP clients for current buffer
+-- @param bufnr int? buffer number
+-- @return table all active LSP client names
+M.list_lsp_clients = function(bufnr)
+ local clients = vim.lsp.buf_get_clients(bufnr)
+ local names = {}
+
+ for _, client in ipairs(clients) do
+ table.insert(names, client.name)
+ end
+
+ return names
+end
+
+return M
diff --git a/home/vim/plugin/abbreviations.lua b/home/vim/plugin/abbreviations.lua
new file mode 100644
index 0000000..f6d6ac3
--- /dev/null
+++ b/home/vim/plugin/abbreviations.lua
@@ -0,0 +1,9 @@
+local abbreviations = {
+ -- A few things that are hard to write in ASCII
+ ["(R)"] = "©",
+ ["(TM)"] = "â„¢",
+}
+
+for text, result in pairs(abbreviations) do
+ vim.cmd.abbreviate(text, result)
+end
diff --git a/home/vim/plugin/abbreviations.vim b/home/vim/plugin/abbreviations.vim
deleted file mode 100644
index 5d36434..0000000
--- a/home/vim/plugin/abbreviations.vim
+++ /dev/null
@@ -1,5 +0,0 @@
-" A few useful sets of abbreviations
-
-" A few things that are hard to write in ASCII
-abbreviate (R) ©
-abbreviate (TM) â„¢
diff --git a/home/vim/plugin/mappings/leader.vim b/home/vim/plugin/mappings/leader.vim
deleted file mode 100644
index 0aba0b2..0000000
--- a/home/vim/plugin/mappings/leader.vim
+++ /dev/null
@@ -1,6 +0,0 @@
-" Map leader to space (needs the noremap trick to avoid moving the cursor)
-nnoremap
-let mapleader=" "
-
-" Map localleader to '!' (if I want to filter text, I use visual mode)
-let maplocalleader="!"
diff --git a/home/vim/plugin/numbertoggle.lua b/home/vim/plugin/numbertoggle.lua
new file mode 100644
index 0000000..1f97fc8
--- /dev/null
+++ b/home/vim/plugin/numbertoggle.lua
@@ -0,0 +1,23 @@
+-- Show lines numbers
+vim.opt.number = true
+
+local numbertoggle = vim.api.nvim_create_augroup("numbertoggle", { clear = true })
+
+-- Toggle numbers between relative and absolute when changing buffers
+vim.api.nvim_create_autocmd({ "BufEnter", "FocusGained", "InsertLeave", "WinEnter" }, {
+ pattern = "*",
+ group = numbertoggle,
+ command = "if &nu | setlocal rnu | endif",
+})
+vim.api.nvim_create_autocmd({ "BufLeave", "FocusLost", "InsertEnter", "WinLeave" }, {
+ pattern = "*",
+ group = numbertoggle,
+ command = "if &nu | setlocal nornu | endif",
+})
+
+-- Never show the sign column in a terminal buffer
+vim.api.nvim_create_autocmd({ "TermOpen" }, {
+ pattern = "*",
+ group = numbertoggle,
+ command = "setlocal nonu nornu",
+})
diff --git a/home/vim/plugin/numbertoggle.vim b/home/vim/plugin/numbertoggle.vim
deleted file mode 100644
index d9a969d..0000000
--- a/home/vim/plugin/numbertoggle.vim
+++ /dev/null
@@ -1,13 +0,0 @@
-" Idea for toggling taken from jeffkreeftmeijer
-
-" Show line numbers
-set number
-
-augroup numbertoggle
- autocmd!
- " Toggle numbers between relative and absolute when changing buffers
- autocmd BufEnter,FocusGained,InsertLeave,WinEnter * if &nu | set rnu | endif
- autocmd BufLeave,FocusLost,InsertEnter,WinLeave * if &nu | set nornu | endif
- " Disable line numbers and relative line numbers in terminal
- autocmd TermOpen * setlocal nonu nornu
-augroup END
diff --git a/home/vim/plugin/settings/ale.vim b/home/vim/plugin/settings/ale.vim
deleted file mode 100644
index 23c9138..0000000
--- a/home/vim/plugin/settings/ale.vim
+++ /dev/null
@@ -1,24 +0,0 @@
-" Always display the sign column to avoid moving the buffer all the time
-let g:ale_sign_column_always=1
-
-" Change the way ALE display messages
-let g:ale_echo_msg_info_str='I'
-let g:ale_echo_msg_warning_str='W'
-let g:ale_echo_msg_error_str='E'
-
-" The message displayed in the command line area
-let g:ale_echo_msg_format='[%linter%][%severity%]%(code):% %s'
-
-" The message displayed in the location list
-let g:ale_loclist_msg_format='[%linter%]%(code):% %s'
-
-" Don't lint every time I change the buffer
-let g:ale_lint_on_text_changed=0
-" Don't lint on leaving insert mode
-let g:ale_lint_on_insert_leave=0
-" Don't lint on entering a buffer
-let g:ale_lint_on_enter=0
-" Do lint on save
-let g:ale_lint_on_save=1
-" Lint on changing the filetype
-let g:ale_lint_on_filetype_changed=1
diff --git a/home/vim/plugin/settings/completion.lua b/home/vim/plugin/settings/completion.lua
new file mode 100644
index 0000000..2d150e8
--- /dev/null
+++ b/home/vim/plugin/settings/completion.lua
@@ -0,0 +1,62 @@
+-- Show completion menu in all cases, and don't select anything
+vim.opt.completeopt = { "menu", "menuone", "noselect" }
+
+local cmp = require("cmp")
+local cmp_under_comparator = require("cmp-under-comparator")
+local luasnip = require("luasnip")
+
+cmp.setup({
+ snippet = {
+ expand = function(args)
+ luasnip.lsp_expand(args.body)
+ end,
+ },
+ mapping = {
+ [""] = function(fallback)
+ if luasnip.expand_or_jumpable() then
+ luasnip.expand_or_jump()
+ else
+ fallback()
+ end
+ end,
+ [""] = function(fallback)
+ if luasnip.jumpable(-1) then
+ luasnip.jump(-1)
+ else
+ fallback()
+ end
+ end,
+ [""] = cmp.mapping(cmp.mapping.select_next_item({ behavior = cmp.SelectBehavior.Insert }), { "i", "c" }),
+ [""] = cmp.mapping(cmp.mapping.select_prev_item({ behavior = cmp.SelectBehavior.Insert }), { "i", "c" }),
+ [""] = cmp.mapping.scroll_docs(-5),
+ [""] = cmp.mapping.scroll_docs(5),
+ [""] = cmp.mapping.confirm({ behavior = cmp.ConfirmBehavior.Insert, select = false }),
+ [""] = cmp.mapping.abort(),
+ },
+ view = {
+ entries = "native",
+ },
+ sources = {
+ { name = "path", priority_weight = 110 },
+ { name = "nvim_lsp", priority_weight = 100 },
+ { name = "nvim_lua", priority_weight = 90 },
+ { name = "luasnip", priority_weight = 80 },
+ { name = "buffer", max_item_count = 5, priority_weight = 50 },
+ },
+ sorting = {
+ priority_weight = 100,
+ comparators = {
+ cmp.config.compare.offset,
+ cmp.config.compare.exact,
+ cmp.config.compare.score,
+ cmp_under_comparator.under,
+ cmp.config.compare.kind,
+ cmp.config.compare.sort_text,
+ cmp.config.compare.length,
+ cmp.config.compare.order,
+ },
+ },
+ experimental = {
+ ghost_text = true,
+ },
+})
diff --git a/home/vim/plugin/settings/dressing.lua b/home/vim/plugin/settings/dressing.lua
new file mode 100644
index 0000000..3928a59
--- /dev/null
+++ b/home/vim/plugin/settings/dressing.lua
@@ -0,0 +1,6 @@
+local dressing = require("dressing")
+
+dressing.setup({
+ -- Use a relative prompt size
+ prefer_width = 0.4,
+})
diff --git a/home/vim/plugin/settings/fastfold.lua b/home/vim/plugin/settings/fastfold.lua
new file mode 100644
index 0000000..78ee937
--- /dev/null
+++ b/home/vim/plugin/settings/fastfold.lua
@@ -0,0 +1,5 @@
+-- Intercept all fold commands
+-- stylua: ignore
+vim.g.fastfold_fold_command_suffixes = {
+ "x", "X", "a", "A", "o", "O", "c", "C", "r", "R", "m", "M", "i", "n", "N",
+}
diff --git a/home/vim/plugin/settings/fastfold.vim b/home/vim/plugin/settings/fastfold.vim
deleted file mode 100644
index a1f1787..0000000
--- a/home/vim/plugin/settings/fastfold.vim
+++ /dev/null
@@ -1,5 +0,0 @@
-" Intercept all fold commands
-let g:fastfold_fold_command_suffixes=[
- \ 'x', 'X', 'a', 'A', 'o', 'O', 'c', 'C',
- \ 'r', 'R', 'm', 'M', 'i', 'n', 'N'
- \ ]
diff --git a/home/vim/plugin/settings/formatting.lua b/home/vim/plugin/settings/formatting.lua
new file mode 100644
index 0000000..1bb896b
--- /dev/null
+++ b/home/vim/plugin/settings/formatting.lua
@@ -0,0 +1,3 @@
+local lsp_format = require("lsp-format")
+
+lsp_format.setup({})
diff --git a/home/vim/plugin/settings/fzf.vim b/home/vim/plugin/settings/fzf.vim
deleted file mode 100644
index 7125b70..0000000
--- a/home/vim/plugin/settings/fzf.vim
+++ /dev/null
@@ -1,8 +0,0 @@
-" Use a floating window when availble
-if has('nvim-0.4.0') || has("patch-8.2.0191")
- let g:fzf_layout = { 'window': {
- \ 'width': 0.9,
- \ 'height': 0.7,
- \ 'highlight': 'Comment',
- \ 'rounded': v:false } }
-endif
diff --git a/home/vim/plugin/settings/git.lua b/home/vim/plugin/settings/git.lua
new file mode 100644
index 0000000..4dbebca
--- /dev/null
+++ b/home/vim/plugin/settings/git.lua
@@ -0,0 +1,58 @@
+local gitsigns = require("gitsigns")
+local wk = require("which-key")
+
+gitsigns.setup({
+ current_line_blame_opts = {
+ -- Show the blame quickly
+ delay = 100,
+ },
+})
+
+local keys = {
+ -- Navigation
+ ["[c"] = { "&diff ? '[c' : 'Gitsigns prev_hunk'", "Previous hunk/diff", expr = true },
+ ["]c"] = { "&diff ? ']c' : 'Gitsigns next_hunk'", "Next hunk/diff", expr = true },
+
+ -- Commands
+ ["g"] = {
+ name = "Git",
+ -- Actions
+ b = { gitsigns.toggle_current_line_blame, "Toggle blame virtual text" },
+ d = { gitsigns.diffthis, "Diff buffer" },
+ -- stylua: ignore
+ D = { function() gitsigns.diffthis("~") end, "Diff buffer against last commit" },
+ g = { "Git", "Git status" },
+ h = { gitsigns.toggle_deleted, "Show deleted hunks" },
+ L = { ":spT:Gllog --follow -- %:p", "Current buffer log" },
+ m = { "(git-messenger)", "Current line blame" },
+ p = { gitsigns.preview_hunk, "Preview hunk" },
+ r = { gitsigns.reset_hunk, "Restore hunk" },
+ R = { gitsigns.reset_buffer, "Restore buffer" },
+ s = { gitsigns.stage_hunk, "Stage hunk" },
+ S = { gitsigns.stage_buffer, "Stage buffer" },
+ u = { gitsigns.undo_stage_hunk, "Undo stage hunk" },
+ ["["] = { gitsigns.prev_hunk, "Previous hunk" },
+ ["]"] = { gitsigns.next_hunk, "Next hunk" },
+ },
+}
+
+local objects = {
+ ["ih"] = { gitsigns.select_hunk, "Git hunk" },
+}
+
+local visual = {
+ ["ih"] = { gitsigns.select_hunk, "Git hunk" },
+
+ -- Only the actual command can make use of the visual selection...
+ ["g"] = {
+ name = "Git",
+ p = { ":Gitsigns preview_hunk", "Preview selection" },
+ r = { ":Gitsigns reset_hunk", "Restore selection" },
+ s = { ":Gitsigns stage_hunk", "Stage selection" },
+ u = { ":Gitsigns undo_stage_hunk", "Undo stage selection" },
+ },
+}
+
+wk.register(keys, { buffer = bufnr })
+wk.register(objects, { buffer = bufnr, mode = "o" })
+wk.register(visual, { buffer = bufnr, mode = "x" })
diff --git a/home/vim/plugin/settings/gruvbox.vim b/home/vim/plugin/settings/gruvbox.vim
deleted file mode 100644
index 4b14437..0000000
--- a/home/vim/plugin/settings/gruvbox.vim
+++ /dev/null
@@ -1,5 +0,0 @@
-" Use the high-contrast theme
-let g:gruvbox_contrast_dark='hard'
-
-" Enable italics because urxvt supports them
-let g:gruvbox_italic=1
diff --git a/home/vim/plugin/settings/lightline.vim b/home/vim/plugin/settings/lightline.vim
deleted file mode 100644
index 0513160..0000000
--- a/home/vim/plugin/settings/lightline.vim
+++ /dev/null
@@ -1,81 +0,0 @@
-" Initialise light-line setting structure
-let g:lightline={}
-
-" Use the wombat colorscheme
-let g:lightline.colorscheme='onedark'
-
-" Status-line for active buffer
-let g:lightline.active={
- \ 'left': [
- \ [ 'mode', 'paste' ],
- \ [ 'gitbranch', 'readonly', 'filename', 'modified' ],
- \ [ 'spell' ],
- \ ],
- \ 'right': [
- \ [ 'lineinfo' ],
- \ [ 'percent' ],
- \ [ 'fileformat', 'fileencoding', 'filetype' ],
- \ [ 'linter_check', 'linter_errors', 'linter_warn', 'linter_ok' ],
- \ [ 'ctags_status' ],
- \ ]
- \ }
-
-" Status-line for inactive buffer
-let g:lightline.inactive={
- \ 'left': [
- \ [ 'filename' ],
- \ ],
- \ 'right': [
- \ [ 'lineinfo' ],
- \ [ 'percent' ],
- \ ],
- \ }
-
-" Which component should be written using which function
-let g:lightline.component_function={
- \ 'readonly': 'LightlineReadonly',
- \ 'modified': 'LightlineModified',
- \ 'gitbranch': 'LightlineFugitive',
- \ }
-
-" Which component can be expanded by using which function
-let g:lightline.component_expand={
- \ 'linter_check': 'lightline#ale#checking',
- \ 'linter_warn': 'lightline#ale#warnings',
- \ 'linter_errors': 'lightline#ale#errors',
- \ 'linter_ok': 'lightline#ale#ok',
- \ }
-
-" How to color custom components
-let g:lightline.component_type={
- \ 'readonly': 'error',
- \ 'linter_checking': 'left',
- \ 'linter_warn': 'warning',
- \ 'linter_errors': 'error',
- \ 'linter_ok': 'left',
- \ }
-
-" Show pretty icons instead of text for linting status
-let g:lightline#ale#indicator_checking='â³'
-let g:lightline#ale#indicator_warnings='â—†'
-let g:lightline#ale#indicator_errors='✗'
-let g:lightline#ale#indicator_ok='✓'
-
-" Show a lock icon when editing a read-only file when it makes sense
-function! LightlineReadonly()
- return &ft!~?'help\|vimfiler\|netrw' && &readonly ? 'î‚¢' : ''
-endfunction
-
-" Show a '+' when the buffer is modified, '-' if not, when it makes sense
-function! LightlineModified()
- return &ft=~'help\|vimfiler\|netrw' ? '' : &modified ? '+' : &modifiable ? '' : '-'
-endfunction
-
-" Show branch name with nice icon in status line, when it makes sense
-function! LightlineFugitive()
- if &ft!~?'help\|vimfiler\|netrw' && exists('*fugitive#head')
- let branch=fugitive#head()
- return branch!=#'' ? 'î‚ '.branch : ''
- endif
- return ''
-endfunction
diff --git a/home/vim/plugin/settings/lsp-lines.lua b/home/vim/plugin/settings/lsp-lines.lua
new file mode 100644
index 0000000..9c79818
--- /dev/null
+++ b/home/vim/plugin/settings/lsp-lines.lua
@@ -0,0 +1,3 @@
+local lsp_lines = require("lsp_lines")
+
+lsp_lines.setup()
diff --git a/home/vim/plugin/settings/lspconfig.lua b/home/vim/plugin/settings/lspconfig.lua
new file mode 100644
index 0000000..794a765
--- /dev/null
+++ b/home/vim/plugin/settings/lspconfig.lua
@@ -0,0 +1,61 @@
+local lspconfig = require("lspconfig")
+local lsp = require("ambroisie.lsp")
+local utils = require("ambroisie.utils")
+
+-- Diagnostics
+vim.diagnostic.config({
+ -- Disable virtual test next to affected regions
+ virtual_text = false,
+ -- Also disable virtual diagnostics under the affected regions
+ virtual_lines = false,
+ -- Show diagnostics signs
+ signs = true,
+ -- Underline offending regions
+ underline = true,
+ -- Do not bother me in the middle of insertion
+ update_in_insert = false,
+ -- Show highest severity first
+ severity_sort = true,
+})
+
+-- Inform servers we are able to do completion, snippets, etc...
+local capabilities = require("cmp_nvim_lsp").default_capabilities()
+
+-- C/C++
+if utils.is_executable("clangd") then
+ lspconfig.clangd.setup({
+ capabilities = capabilities,
+ on_attach = lsp.on_attach,
+ })
+end
+
+-- Nix
+if utils.is_executable("nil") then
+ lspconfig.nil_ls.setup({
+ capabilities = capabilities,
+ on_attach = lsp.on_attach,
+ })
+end
+
+if utils.is_executable("rnix-lsp") then
+ lspconfig.rnix.setup({
+ capabilities = capabilities,
+ on_attach = lsp.on_attach,
+ })
+end
+
+-- Python
+if utils.is_executable("pyright") then
+ lspconfig.pyright.setup({
+ capabilities = capabilities,
+ on_attach = lsp.on_attach,
+ })
+end
+
+-- Rust
+if utils.is_executable("rust-analyzer") then
+ lspconfig.rust_analyzer.setup({
+ capabilities = capabilities,
+ on_attach = lsp.on_attach,
+ })
+end
diff --git a/home/vim/plugin/settings/lualine.lua b/home/vim/plugin/settings/lualine.lua
new file mode 100644
index 0000000..fdaccda
--- /dev/null
+++ b/home/vim/plugin/settings/lualine.lua
@@ -0,0 +1,61 @@
+local lualine = require("lualine")
+local utils = require("ambroisie.utils")
+
+local function list_spell_languages()
+ if not vim.opt.spell:get() then
+ return ""
+ end
+
+ return table.concat(vim.opt.spelllang:get(), ", ")
+end
+
+local function list_lsp_clients()
+ local client_names = utils.list_lsp_clients()
+
+ if #client_names == 0 then
+ return ""
+ end
+
+ return "[ " .. table.concat(client_names, " ") .. " ]"
+end
+
+lualine.setup({
+ options = {
+ icons_enabled = false,
+ section_separators = "",
+ component_separators = "|",
+ },
+ sections = {
+ lualine_a = {
+ { "mode" },
+ },
+ lualine_b = {
+ { "FugitiveHead" },
+ { "filename", symbols = { readonly = "🔒" } },
+ },
+ lualine_c = {
+ { list_spell_languages },
+ { "lsp_progress" },
+ },
+ lualine_x = {
+ { list_lsp_clients },
+ {
+ "diagnostics",
+ -- Only use the diagnostics API
+ sources = { "nvim_diagnostic" },
+ },
+ },
+ lualine_y = {
+ { "fileformat" },
+ { "encoding" },
+ { "filetype" },
+ },
+ lualine_z = {
+ "location",
+ },
+ },
+ extensions = {
+ "fugitive",
+ "quickfix",
+ },
+})
diff --git a/home/vim/plugin/settings/luasnip.lua b/home/vim/plugin/settings/luasnip.lua
new file mode 100644
index 0000000..80309d7
--- /dev/null
+++ b/home/vim/plugin/settings/luasnip.lua
@@ -0,0 +1 @@
+require("luasnip.loaders.from_vscode").lazy_load()
diff --git a/home/vim/plugin/settings/notify.lua b/home/vim/plugin/settings/notify.lua
new file mode 100644
index 0000000..b68c90f
--- /dev/null
+++ b/home/vim/plugin/settings/notify.lua
@@ -0,0 +1,14 @@
+local notify = require("notify")
+
+notify.setup({
+ icons = {
+ DEBUG = "D",
+ ERROR = "E",
+ INFO = "I",
+ TRACE = "T",
+ WARN = "W",
+ },
+ stages = "slide",
+})
+
+vim.notify = notify
diff --git a/home/vim/plugin/settings/null-ls.lua b/home/vim/plugin/settings/null-ls.lua
new file mode 100644
index 0000000..0eaa55c
--- /dev/null
+++ b/home/vim/plugin/settings/null-ls.lua
@@ -0,0 +1,138 @@
+local null_ls = require("null-ls")
+local lsp = require("ambroisie.lsp")
+local utils = require("ambroisie.utils")
+
+null_ls.setup({
+ on_attach = lsp.on_attach,
+})
+
+-- Bazel
+null_ls.register({
+ null_ls.builtins.diagnostics.buildifier.with({
+ -- Only used if available
+ condition = utils.is_executable_condition("buildifier"),
+ }),
+ null_ls.builtins.formatting.buildifier.with({
+ -- Only used if available
+ condition = utils.is_executable_condition("buildifier"),
+ }),
+})
+
+-- C, C++
+null_ls.register({
+ null_ls.builtins.formatting.clang_format.with({
+ -- Only used if available, but prefer clangd formatting if available
+ condition = function()
+ return utils.is_executable("clang-format") and not utils.is_executable("clangd")
+ end,
+ }),
+})
+
+-- Haskell
+null_ls.register({
+ null_ls.builtins.formatting.brittany.with({
+ -- Only used if available
+ condition = utils.is_executable_condition("brittany"),
+ }),
+})
+
+-- Nix
+null_ls.register({
+ null_ls.builtins.formatting.nixpkgs_fmt.with({
+ -- Only used if available, but prefer rnix if available
+ condition = function()
+ return utils.is_executable("nixpkgs-fmt")
+ and not utils.is_executable("rnix-lsp")
+ and not utils.is_executable("nil")
+ end,
+ }),
+})
+
+-- Python
+null_ls.register({
+ null_ls.builtins.diagnostics.flake8.with({
+ -- Only used if available, but prefer pflake8 if available
+ condition = function()
+ return utils.is_executable("flake8") and not utils.is_executable("pflake8")
+ end,
+ }),
+ null_ls.builtins.diagnostics.pyproject_flake8.with({
+ -- Only used if available
+ condition = utils.is_executable_condition("pflake8"),
+ }),
+ null_ls.builtins.diagnostics.mypy.with({
+ -- Only used if available
+ condition = utils.is_executable_condition("mypy"),
+ }),
+ null_ls.builtins.diagnostics.pylint.with({
+ -- Only used if available
+ condition = utils.is_executable_condition("pylint"),
+ }),
+ null_ls.builtins.formatting.black.with({
+ extra_args = { "--fast" },
+ -- Only used if available
+ condition = utils.is_executable_condition("black"),
+ }),
+ null_ls.builtins.formatting.isort.with({
+ -- Only used if available
+ condition = utils.is_executable_condition("isort"),
+ }),
+})
+
+-- Shell (non-POSIX)
+null_ls.register({
+ null_ls.builtins.code_actions.shellcheck.with({
+ -- Restrict to bash and zsh
+ filetypes = { "bash", "zsh" },
+ -- Only used if available
+ condition = utils.is_executable_condition("shellcheck"),
+ }),
+ null_ls.builtins.diagnostics.shellcheck.with({
+ -- Show error code in message
+ diagnostics_format = "[#{c}] #{m}",
+ -- Require explicit empty string test, use bash dialect
+ extra_args = { "-s", "bash", "-o", "avoid-nullary-conditions" },
+ -- Restrict to bash and zsh
+ filetypes = { "bash", "zsh" },
+ -- Only used if available
+ condition = utils.is_executable_condition("shellcheck"),
+ }),
+ null_ls.builtins.formatting.shfmt.with({
+ -- Indent with 4 spaces, simplify the code, indent switch cases,
+ -- add space after redirection, use bash dialect
+ extra_args = { "-i", "4", "-s", "-ci", "-sr", "-ln", "bash" },
+ -- Restrict to bash and zsh
+ filetypes = { "bash", "zsh" },
+ -- Only used if available
+ condition = utils.is_executable_condition("shfmt"),
+ }),
+})
+
+-- Shell (POSIX)
+null_ls.register({
+ null_ls.builtins.code_actions.shellcheck.with({
+ -- Restrict to POSIX sh
+ filetypes = { "sh" },
+ -- Only used if available
+ condition = utils.is_executable_condition("shellcheck"),
+ }),
+ null_ls.builtins.diagnostics.shellcheck.with({
+ -- Show error code in message
+ diagnostics_format = "[#{c}] #{m}",
+ -- Require explicit empty string test
+ extra_args = { "-o", "avoid-nullary-conditions" },
+ -- Restrict to POSIX sh
+ filetypes = { "sh" },
+ -- Only used if available
+ condition = utils.is_executable_condition("shellcheck"),
+ }),
+ null_ls.builtins.formatting.shfmt.with({
+ -- Indent with 4 spaces, simplify the code, indent switch cases,
+ -- add space after redirection, use POSIX
+ extra_args = { "-i", "4", "-s", "-ci", "-sr", "-ln", "posix" },
+ -- Restrict to POSIX sh
+ filetypes = { "sh" },
+ -- Only used if available
+ condition = utils.is_executable_condition("shfmt"),
+ }),
+})
diff --git a/home/vim/plugin/settings/pandoc.vim b/home/vim/plugin/settings/pandoc.vim
deleted file mode 100644
index 71b750c..0000000
--- a/home/vim/plugin/settings/pandoc.vim
+++ /dev/null
@@ -1,20 +0,0 @@
-" Which code-block languages should I expect to be high-lighted.
-let g:pandoc#syntax#codeblocks#embeds#langs=[
- \ "bash=sh",
- \ "c",
- \ "cpp",
- \ "go",
- \ "haskell",
- \ "python",
- \ "rust",
- \ "sh",
- \ "vim",
- \ "yaml",
- \ "tex",
- \ "toml",
- \ "perl",
- \ "json",
- \ "latex=tex",
- \ "make",
- \ "makefile=make",
- \ ]
diff --git a/home/vim/plugin/settings/ssh.lua b/home/vim/plugin/settings/ssh.lua
new file mode 100644
index 0000000..992a707
--- /dev/null
+++ b/home/vim/plugin/settings/ssh.lua
@@ -0,0 +1,17 @@
+if not require("ambroisie.utils").is_ssh() then
+ return
+end
+
+local function copy(lines, _)
+ require("osc52").copy(table.concat(lines, "\n"))
+end
+
+local function paste()
+ return { vim.fn.split(vim.fn.getreg(""), "\n"), vim.fn.getregtype("") }
+end
+
+vim.g.clipboard = {
+ name = "osc52",
+ copy = { ["+"] = copy, ["*"] = copy },
+ paste = { ["+"] = paste, ["*"] = paste },
+}
diff --git a/home/vim/plugin/settings/surround.lua b/home/vim/plugin/settings/surround.lua
new file mode 100644
index 0000000..3585a12
--- /dev/null
+++ b/home/vim/plugin/settings/surround.lua
@@ -0,0 +1,3 @@
+require("nvim-surround").setup({
+ -- No configuration at the moment
+})
diff --git a/home/vim/plugin/settings/telescope.lua b/home/vim/plugin/settings/telescope.lua
new file mode 100644
index 0000000..ed8ebc7
--- /dev/null
+++ b/home/vim/plugin/settings/telescope.lua
@@ -0,0 +1,25 @@
+local telescope = require("telescope")
+
+telescope.setup({
+ defaults = {
+ mappings = {
+ i = {
+ [""] = "which_key",
+ -- I want the normal readline mappings rather than scrolling
+ [""] = false,
+ },
+ },
+ },
+ extensions = {
+ fzf = {
+ fuzzy = true,
+ override_generic_sorter = true,
+ override_file_sorter = true,
+ case_mode = "smart_case",
+ },
+ },
+})
+
+telescope.load_extension("fzf")
+telescope.load_extension("lsp_handlers")
+telescope.load_extension("notify")
diff --git a/home/vim/plugin/settings/tree-sitter.lua b/home/vim/plugin/settings/tree-sitter.lua
new file mode 100644
index 0000000..0d84abd
--- /dev/null
+++ b/home/vim/plugin/settings/tree-sitter.lua
@@ -0,0 +1,56 @@
+local ts_config = require("nvim-treesitter.configs")
+ts_config.setup({
+ highlight = {
+ enable = true,
+ -- Avoid duplicate highlighting
+ additional_vim_regex_highlighting = false,
+ },
+ indent = {
+ enable = true,
+ },
+ context_commentstring = {
+ enable = true,
+ },
+ textobjects = {
+ select = {
+ enable = true,
+ -- Jump to matching text objects
+ lookahead = true,
+ keymaps = {
+ ["aa"] = "@parameter.outer",
+ ["ia"] = "@parameter.inner",
+ ["ab"] = "@block.outer",
+ ["ib"] = "@block.inner",
+ ["ac"] = "@class.outer",
+ ["ic"] = "@class.inner",
+ ["af"] = "@function.outer",
+ ["if"] = "@function.inner",
+ ["ak"] = "@comment.outer",
+ ["aS"] = "@statement.outer",
+ },
+ },
+ move = {
+ enable = true,
+ -- Add to jump list
+ set_jumps = true,
+ goto_next_start = {
+ ["]m"] = "@function.outer",
+ ["]S"] = "@statement.outer",
+ ["]]"] = "@class.outer",
+ },
+ goto_next_end = {
+ ["]M"] = "@function.outer",
+ ["]["] = "@class.outer",
+ },
+ goto_previous_start = {
+ ["[m"] = "@function.outer",
+ ["[S"] = "@statement.outer",
+ ["[["] = "@class.outer",
+ },
+ goto_previous_end = {
+ ["[M"] = "@function.outer",
+ ["[]"] = "@class.outer",
+ },
+ },
+ },
+})
diff --git a/home/vim/plugin/settings/which-key.lua b/home/vim/plugin/settings/which-key.lua
new file mode 100644
index 0000000..2edfd70
--- /dev/null
+++ b/home/vim/plugin/settings/which-key.lua
@@ -0,0 +1,2 @@
+local wk = require("which-key")
+wk.setup()
diff --git a/home/vim/plugin/signtoggle.lua b/home/vim/plugin/signtoggle.lua
new file mode 100644
index 0000000..d6a26e2
--- /dev/null
+++ b/home/vim/plugin/signtoggle.lua
@@ -0,0 +1,20 @@
+local signtoggle = vim.api.nvim_create_augroup("signtoggle", { clear = true })
+
+-- Only show sign column for the currently focused buffer
+vim.api.nvim_create_autocmd({ "BufEnter", "FocusGained", "WinEnter" }, {
+ pattern = "*",
+ group = signtoggle,
+ command = "setlocal signcolumn=yes",
+})
+vim.api.nvim_create_autocmd({ "BufLeave", "FocusLost", "WinLeave" }, {
+ pattern = "*",
+ group = signtoggle,
+ command = "setlocal signcolumn=yes",
+})
+
+-- Never show the sign column in a terminal buffer
+vim.api.nvim_create_autocmd({ "TermOpen" }, {
+ pattern = "*",
+ group = signtoggle,
+ command = "setlocal signcolumn=no",
+})
diff --git a/home/vim/plugin/signtoggle.vim b/home/vim/plugin/signtoggle.vim
deleted file mode 100644
index c2f0183..0000000
--- a/home/vim/plugin/signtoggle.vim
+++ /dev/null
@@ -1,8 +0,0 @@
-augroup signtoggle
- autocmd!
- " Only show the sign column for the current focused buffer
- autocmd BufEnter,FocusGained,WinEnter * set signcolumn=yes
- autocmd BufLeave,FocusLost,WinLeave * set signcolumn=no
- " Disable the sign column in terminal
- autocmd TermOpen * setlocal signcolumn=no
-augroup END
diff --git a/home/wm/cursor/default.nix b/home/wm/cursor/default.nix
new file mode 100644
index 0000000..9426232
--- /dev/null
+++ b/home/wm/cursor/default.nix
@@ -0,0 +1,23 @@
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.my.home.wm.cursor;
+
+ cfg_x = config.my.home.x;
+ cfg_gtk = config.my.home.gtk;
+in
+{
+ config = lib.mkIf cfg.enable {
+ home.pointerCursor = {
+ package = pkgs.ambroisie.vimix-cursors;
+ name = "Vimix-cursors";
+
+ x11 = {
+ inherit (cfg_x) enable;
+ };
+
+ gtk = {
+ inherit (cfg_gtk) enable;
+ };
+ };
+ };
+}
diff --git a/home/wm/default.nix b/home/wm/default.nix
new file mode 100644
index 0000000..6a615e5
--- /dev/null
+++ b/home/wm/default.nix
@@ -0,0 +1,92 @@
+{ config, lib, pkgs, ... }:
+let
+ mkRelatedOption = description: relatedWMs:
+ let
+ isActivatedWm = wm: config.my.home.wm.windowManager == wm;
+ in
+ (lib.mkEnableOption description) // {
+ default = builtins.any isActivatedWm relatedWMs;
+ };
+in
+{
+ imports = [
+ ./cursor
+ ./dunst
+ ./i3
+ ./i3bar
+ ./rofi
+ ./screen-lock
+ ];
+
+ options.my.home.wm = with lib; {
+ windowManager = mkOption {
+ type = with types; nullOr (enum [ "i3" ]);
+ default = null;
+ example = "i3";
+ description = "Which window manager to use for home session";
+ };
+
+ cursor = {
+ enable = mkRelatedOption "dunst configuration" [ "i3" ];
+ };
+
+ dunst = {
+ enable = mkRelatedOption "dunst configuration" [ "i3" ];
+ };
+
+ i3bar = {
+ enable = mkRelatedOption "i3bar configuration" [ "i3" ];
+ };
+
+ rofi = {
+ enable = mkRelatedOption "rofi menu" [ "i3" ];
+ };
+
+ screen-lock = {
+ enable = mkRelatedOption "automatic X screen locker" [ "i3" ];
+
+ command = mkOption {
+ type = types.str;
+ default = "${lib.getExe pkgs.i3lock} -n -c 000000";
+ example = "\${lib.getExe pkgs.i3lock} -n -i lock.png";
+ description = "Locker command to run";
+ };
+
+ cornerLock = {
+ enable = my.mkDisableOption ''
+ Move mouse to upper-left corner to lock instantly, lower-right corner to
+ disable auto-lock.
+ '';
+
+ delay = mkOption {
+ type = types.int;
+ default = 5;
+ example = 15;
+ description = "How many seconds before locking this way";
+ };
+ };
+
+ notify = {
+ enable = my.mkDisableOption "Notify when about to lock the screen";
+
+ delay = mkOption {
+ type = types.int;
+ default = 5;
+ example = 15;
+ description = ''
+ How many seconds in advance should there be a notification.
+ This value must be at lesser than or equal to `cornerLock.delay`
+ when both options are enabled.
+ '';
+ };
+ };
+
+ timeout = mkOption {
+ type = types.ints.between 1 60;
+ default = 15;
+ example = 1;
+ description = "Inactive time interval to lock the screen automatically";
+ };
+ };
+ };
+}
diff --git a/home/wm/dunst/default.nix b/home/wm/dunst/default.nix
new file mode 100644
index 0000000..949db32
--- /dev/null
+++ b/home/wm/dunst/default.nix
@@ -0,0 +1,73 @@
+{ config, lib, ... }:
+let
+ cfg = config.my.home.wm.dunst;
+in
+{
+ config = lib.mkIf cfg.enable {
+ services.dunst = {
+ enable = true;
+
+ settings = {
+ global = {
+ alignment = "center"; # Put message in the middle of the box
+ browser = "xdg-open"; # use default browser to open links
+ dmenu =
+ lib.mkIf
+ config.my.home.wm.rofi.enable
+ "rofi -p dunst -dmenu"; # use rofi for menu
+ follow = "keyboard"; # follow keyboard focus
+ font = "Monospace 8"; # Simple looking font
+ frame_width = 3; # small frame
+ markup = "full"; # subset of HTML
+ max_icon_size = 32; # avoid icons that are too big
+ padding = 6; # distance between text and bubble border
+ progress_bar = true; # show a progress bar in notification bubbles
+ separator_color = "frame"; # use frame color to separate bubbles
+ sort = true; # sort messages by urgency
+ word_wrap = true; # Break long lines to make them readable
+
+ # Fixed size notifications, slightly recessed from the top right
+ width = 300;
+ height = 50;
+ origin = "top-right";
+ offset = "15x50";
+ };
+
+ urgency_low = {
+ background = "#191311";
+ foreground = "#3b7c87";
+ frame_color = "#3b7c87";
+ highlight = "#4998a6";
+ timeout = 10;
+ };
+
+ urgency_normal = {
+ background = "#191311";
+ foreground = "#5b8234";
+ frame_color = "#5b8234";
+ highlight = "#73a542";
+ timeout = 10;
+ };
+
+ urgency_critical = {
+ background = "#191311";
+ foreground = "#b7472a";
+ frame_color = "#b7472a";
+ highlight = "#d25637";
+ timeout = 0;
+ };
+
+ fullscreen_delay_everything = {
+ # delay notifications by default
+ fullscreen = "delay";
+ };
+
+ fullscreen_show_critical = {
+ # show critical notification
+ fullscreen = "show";
+ msg_urgency = "critical";
+ };
+ };
+ };
+ };
+}
diff --git a/home/wm/i3/default.nix b/home/wm/i3/default.nix
new file mode 100644
index 0000000..d1dbe2c
--- /dev/null
+++ b/home/wm/i3/default.nix
@@ -0,0 +1,389 @@
+{ config, lib, pkgs, ... }:
+let
+ isEnabled = config.my.home.wm.windowManager == "i3";
+
+ terminal =
+ if config.my.home.terminal.program != null
+ then config.my.home.terminal.program
+ else "i3-sensible-terminal";
+
+ alt = "Mod1"; # `Alt` key
+ modifier = "Mod4"; # `Super` key
+ movementKeys = [ "Left" "Down" "Up" "Right" ];
+ vimMovementKeys = [ "h" "j" "k" "l" ];
+ shutdownMode =
+ "(l)ock, (e)xit, switch_(u)ser, (h)ibernate, (r)eboot, (Shift+s)hutdown";
+
+ # Takes an attrset of bindings for movement keys, transforms it to Vim keys
+ toVimKeyBindings =
+ let
+ toVimKeys = builtins.replaceStrings movementKeys vimMovementKeys;
+ in
+ lib.my.renameAttrs toVimKeys;
+
+ # Takes an attrset of bindings for movement keys, add equivalent Vim keys
+ addVimKeyBindings = bindings: bindings // (toVimKeyBindings bindings);
+ # Generate an attrset of movement bindings, using the mapper function
+ genMovementBindings = f: addVimKeyBindings (lib.my.genAttrs' movementKeys f);
+
+ # Used in multiple scripts to show messages through keybindings
+ notify-send = lib.getExe pkgs.libnotify;
+
+ # Screen backlight management
+ changeBacklight = lib.getExe pkgs.ambroisie.change-backlight;
+
+ # Audio and volume management
+ changeAudio = lib.getExe pkgs.ambroisie.change-audio;
+
+ # Lock management
+ toggleXautolock =
+ let
+ systemctlUser = "${pkgs.systemd}/bin/systemctl --user";
+ notify = "${notify-send} -u low"
+ + " -h string:x-canonical-private-synchronous:xautolock-toggle";
+ in
+ pkgs.writeScript "toggle-xautolock" ''
+ #!/bin/sh
+ if ${systemctlUser} is-active xautolock-session.service; then
+ ${systemctlUser} stop --user xautolock-session.service
+ xset s off
+ ${notify} "Disabled Xautolock"
+ else
+ ${systemctlUser} start xautolock-session.service
+ xset s on
+ ${notify} "Enabled Xautolock"
+ fi
+ '';
+in
+{
+ config = lib.mkIf isEnabled {
+ home.packages = with pkgs; [
+ ambroisie.dragger # drag-and-drop from the CLI
+ ambroisie.i3-get-window-criteria # little helper for i3 configuration
+ arandr # Used by a mapping
+ playerctl # Used by a mapping
+ xdotool # Used by 'rofi-rbw', in a mapping
+ ];
+
+ xsession.windowManager.i3 = {
+ enable = true;
+
+ config = {
+ inherit modifier;
+
+ bars =
+ let
+ i3status-rs = lib.getExe config.programs.i3status-rust.package;
+ in
+ assert [ "top" ] == lib.attrNames config.programs.i3status-rust.bars;
+ [
+ {
+ statusCommand = "${i3status-rs} config-top.toml";
+ trayOutput = "primary";
+ position = "top";
+
+ colors = {
+ background = "#021215";
+ statusline = "#93a1a1";
+ separator = "#2aa198";
+
+ focusedWorkspace = {
+ border = "#2aa198";
+ background = "#073642";
+ text = "#eee895";
+ };
+
+ activeWorkspace = {
+ border = "#073642";
+ background = "#002b36";
+ text = "#839496";
+ };
+
+ inactiveWorkspace = {
+ border = "#002b36";
+ background = "#021215";
+ text = "#586e75";
+ };
+
+ urgentWorkspace = {
+ border = "#cb4b16";
+ background = "#dc322f";
+ text = "#fdf6e3";
+ };
+ };
+
+ fonts = {
+ names = [ "DejaVu Sans Mono" "FontAwesome5Free" ];
+ size = 8.0;
+ };
+ }
+ ];
+
+ floating = {
+ inherit modifier;
+
+ criteria = [
+ { class = "^tridactyl_editor$"; }
+ { class = "^Blueman-.*$"; }
+ { title = "^htop$"; }
+ { class = "^Thunderbird$"; instance = "Mailnews"; window_role = "filterlist"; }
+ { class = "^Pavucontrol.*$"; }
+ { class = "^Arandr$"; }
+ ];
+ };
+
+ focus = {
+ followMouse = true; # It is annoying sometimes, but useful enough to use
+ mouseWarping = true; # Let's moving around when switching screens
+ };
+
+ fonts = {
+ names = [ "DejaVu Sans Mono" ];
+ size = 8.0;
+ };
+
+ # I don't care for i3's default values, I specify them all explicitly
+ keybindings = lib.my.recursiveMerge [
+ {
+ # The basics
+ "${modifier}+Return" = "exec ${terminal} ${
+ lib.optionalString config.my.home.tmux.enable "-e tmux new-session"
+ }";
+ "${modifier}+Shift+Return" = "exec env TMUX=nil ${terminal}";
+ "${modifier}+Shift+q" = "kill";
+ "${modifier}+f" = "fullscreen toggle";
+ "${modifier}+Shift+c" = "reload";
+ "${modifier}+Shift+r" = "restart";
+ "${modifier}+Shift+e" =
+ "exec i3-nagbar -t warning -m 'Do you want to exit i3?' -b 'Yes' 'i3-msg exit'";
+ }
+ {
+ # Splits
+ "${modifier}+g" = "split h"; # Horizontally
+ "${modifier}+v" = "split v"; # Vertically
+ }
+ {
+ # Layouts
+ "${modifier}+s" = "layout stacking";
+ "${modifier}+w" = "layout tabbed";
+ "${modifier}+e" = "layout toggle split";
+ }
+ {
+ # Toggle tiling/floating
+ "${modifier}+Control+space" = "floating toggle";
+ # Change focus between tiling/floating
+ "${modifier}+space" = "focus mode_toggle";
+ # Center floating window
+ "${modifier}+c" = "move absolute position center";
+ # Center floating window
+ "${modifier}+Shift+s" = "sticky toggle";
+ }
+ {
+ # Focus parent container
+ "${modifier}+q" = "focus parent";
+ # Focus child container
+ "${modifier}+a" = "focus child";
+ }
+ (lib.optionalAttrs config.my.home.wm.rofi.enable {
+ # Rofi tools
+ "${modifier}+d" = "exec rofi -show drun -disable-history";
+ "${modifier}+Shift+d" = "exec rofi -show run -disable-history";
+ "${modifier}+p" = "exec --no-startup-id flameshot gui";
+ "${modifier}+Ctrl+p" = "exec ${lib.getExe pkgs.rofi-rbw}";
+ "${modifier}+Shift+p" = "exec rofi -show emoji";
+ "${modifier}+b" =
+ let
+ inherit (config.my.home.bluetooth) enable;
+ prog = lib.getExe pkgs.rofi-bluetooth;
+ in
+ lib.mkIf enable "exec ${prog}";
+ })
+ (
+ # Changing container focus
+ genMovementBindings (
+ key: lib.nameValuePair
+ "${modifier}+${key}"
+ "focus ${lib.toLower key}"
+ )
+ )
+ (
+ # Changing screen focus
+ genMovementBindings (
+ key: lib.nameValuePair
+ "${modifier}+${alt}+${key}"
+ "focus output ${lib.toLower key}"
+ )
+ )
+ (
+ # Moving workspace to another screen
+ genMovementBindings (
+ key: lib.nameValuePair
+ "${modifier}+${alt}+Control+${key}"
+ "move workspace to output ${lib.toLower key}"
+ )
+ )
+ (
+ # Moving container to another screen
+ genMovementBindings (
+ key: lib.nameValuePair
+ "${modifier}+${alt}+Shift+${key}"
+ "move container to output ${lib.toLower key}"
+ )
+ )
+ (addVimKeyBindings {
+ # Scroll through workspaces on given screen
+ "${modifier}+Control+Left" = "workspace prev_on_output";
+ "${modifier}+Control+Right" = "workspace next_on_output";
+ # Use scratchpad
+ "${modifier}+Control+Up" = "move to scratchpad";
+ "${modifier}+Control+Down" = "scratchpad show";
+ })
+ (
+ # Moving floating window
+ genMovementBindings (
+ key: lib.nameValuePair
+ "${modifier}+Shift+${key}"
+ "move ${lib.toLower key} 10 px"
+ )
+ )
+ {
+ # Media keys
+ "XF86AudioRaiseVolume" = "exec --no-startup-id ${changeAudio} up 5";
+ "XF86AudioLowerVolume" = "exec --no-startup-id ${changeAudio} down 5";
+ "Control+XF86AudioRaiseVolume" = "exec --no-startup-id ${changeAudio} up 1";
+ "Control+XF86AudioLowerVolume" = "exec --no-startup-id ${changeAudio} down 1";
+
+ "Shift+XF86AudioRaiseVolume" = "exec --no-startup-id ${changeAudio} up --force 5";
+ "Shift+XF86AudioLowerVolume" = "exec --no-startup-id ${changeAudio} down --force 5";
+ "Control+Shift+XF86AudioRaiseVolume" = "exec --no-startup-id ${changeAudio} up --force 1";
+ "Control+Shift+XF86AudioLowerVolume" = "exec --no-startup-id ${changeAudio} down --force 1";
+
+ "XF86AudioMute" = "exec --no-startup-id ${changeAudio} toggle";
+ "XF86AudioMicMute" = "exec --no-startup-id ${changeAudio} toggle mic";
+
+ "XF86AudioPlay" = "exec playerctl play-pause";
+ "XF86AudioNext" = "exec playerctl next";
+ "XF86AudioPrev" = "exec playerctl previous";
+ }
+ {
+ # Screen management
+ "XF86Display" = "exec arandr";
+ "XF86MonBrightnessUp" = "exec --no-startup-id ${changeBacklight} up 10";
+ "XF86MonBrightnessDown" = "exec --no-startup-id ${changeBacklight} down 10";
+ "Control+XF86MonBrightnessUp" = "exec --no-startup-id ${changeBacklight} up 1";
+ "Control+XF86MonBrightnessDown" = "exec --no-startup-id ${changeBacklight} down 1";
+ }
+ {
+ # Sub-modes
+ "${modifier}+r" = "mode resize";
+ "${modifier}+Shift+space" = "mode floating";
+ }
+ (lib.optionalAttrs config.my.home.wm.screen-lock.enable {
+ "${modifier}+x" = "exec ${toggleXautolock}";
+ })
+ (
+ let
+ execDunstctl = "exec ${pkgs.dunst}/bin/dunstctl";
+ in
+ lib.optionalAttrs config.my.home.wm.dunst.enable {
+ "${modifier}+minus" = "${execDunstctl} close";
+ "${modifier}+Shift+minus" = "${execDunstctl} close-all";
+ "${modifier}+equal" = "${execDunstctl} history-pop";
+ }
+ )
+ ];
+
+ keycodebindings =
+ let
+ toKeycode = n: if n == 0 then 19 else n + 9;
+ createWorkspaceBindings = mapping: command:
+ let
+ createWorkspaceBinding = num:
+ lib.nameValuePair
+ "${mapping}+${toString (toKeycode num)}"
+ "${command} ${toString num}";
+ oneToNine = builtins.genList (x: x + 1) 9;
+ in
+ lib.my.genAttrs' oneToNine createWorkspaceBinding;
+ in
+ lib.my.recursiveMerge [
+ (createWorkspaceBindings modifier "workspace number")
+ (createWorkspaceBindings "${modifier}+Shift" "move container to workspace number")
+ {
+ "${modifier}+${toString (toKeycode 0)}" = ''mode "${shutdownMode}"'';
+ }
+ ];
+
+ modes =
+ let
+ makeModeBindings = attrs: (addVimKeyBindings attrs) // {
+ "Escape" = "mode default";
+ "Return" = "mode default";
+ };
+ in
+ {
+ resize = makeModeBindings {
+ # Normal movements
+ "Left" = "resize shrink width 10 px or 10 ppt";
+ "Down" = "resize grow height 10 px or 10 ppt";
+ "Up" = "resize shrink height 10 px or 10 ppt";
+ "Right" = "resize grow width 10 px or 10 ppt";
+ # Small movements
+ "Control+Left" = "resize shrink width 1 px or 1 ppt";
+ "Control+Down" = "resize grow height 1 px or 1 ppt";
+ "Control+Up" = "resize shrink height 1 px or 1 ppt";
+ "Control+Right" = "resize grow width 1 px or 1 ppt";
+ # Big movements
+ "Shift+Left" = "resize shrink width 100 px or 100 ppt";
+ "Shift+Down" = "resize grow height 100 px or 100 ppt";
+ "Shift+Up" = "resize shrink height 100 px or 100 ppt";
+ "Shift+Right" = "resize grow width 100 px or 100 ppt";
+ };
+
+ floating = makeModeBindings {
+ # Normal movements
+ "Left" = "move left 10 px";
+ "Down" = "move down 10 px";
+ "Up" = "move up 10 px";
+ "Right" = "move right 10 px";
+ # Small movements
+ "Control+Left" = "move left 1 px";
+ "Control+Down" = "move down 1 px";
+ "Control+Up" = "move up 1 px";
+ "Control+Right" = "move right 1 px";
+ # Big movements
+ "Shift+Left" = "move left 100 px";
+ "Shift+Down" = "move down 100 px";
+ "Shift+Up" = "move up 100 px";
+ "Shift+Right" = "move right 100 px";
+ };
+
+ ${shutdownMode} = makeModeBindings {
+ "l" = "exec --no-startup-id loginctl lock-session, mode default";
+ "s" = "exec --no-startup-id systemctl suspend, mode default";
+ "u" = "exec --no-startup-id dm-tool switch-to-greeter, mode default";
+ "e" = "exec --no-startup-id i3-msg exit, mode default";
+ "h" = "exec --no-startup-id systemctl hibernate, mode default";
+ "r" = "exec --no-startup-id systemctl reboot, mode default";
+ "Shift+s" = "exec --no-startup-id systemctl poweroff, mode default";
+ };
+ };
+
+ startup = [
+ # FIXME
+ # { commdand; always; notification; }
+ ];
+
+ window = {
+ commands = [
+ # Make htop window bigger
+ {
+ criteria = { title = "^htop$"; };
+ command = "resize set 80 ppt 80 ppt, move position center";
+ }
+ ];
+ };
+ };
+ };
+ };
+}
diff --git a/home/wm/i3bar/default.nix b/home/wm/i3bar/default.nix
new file mode 100644
index 0000000..05b0f50
--- /dev/null
+++ b/home/wm/i3bar/default.nix
@@ -0,0 +1,114 @@
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.my.home.wm.i3bar;
+in
+{
+ config = lib.mkIf cfg.enable {
+ home.packages = with pkgs; [
+ alsa-utils # Used by `sound` block
+ lm_sensors # Used by `temperature` block
+ font-awesome # Icon font
+ ];
+
+ programs.i3status-rust = {
+ enable = true;
+
+ bars = {
+ top = {
+ icons = "awesome5";
+
+ blocks = builtins.filter (attr: attr != { }) [
+ {
+ block = "music";
+ # This format seems to remove the block when not playing, somehow
+ format = "{ $icon $combo.str(max_w:50,rot_interval:0.5) $prev $play $next |}";
+ click = [
+ {
+ button = "play";
+ action = "music_play";
+ }
+ {
+ button = "prev";
+ action = "music_prev";
+ }
+ {
+ button = "next";
+ action = "music_next";
+ }
+ ];
+ }
+ (lib.optionalAttrs config.my.home.bluetooth.enable {
+ block = "bluetooth";
+ mac = "4C:87:5D:06:40:D9";
+ format = " $icon Boson{ $percentage|} ";
+ disconnected_format = "";
+ })
+ (lib.optionalAttrs config.my.home.bluetooth.enable {
+ block = "bluetooth";
+ mac = "38:18:4C:BE:8E:97";
+ format = " $icon Muon{ $percentage|} ";
+ disconnected_format = "";
+ })
+ (lib.optionalAttrs config.my.home.bluetooth.enable {
+ block = "bluetooth";
+ mac = "94:DB:56:00:EE:93";
+ format = " $icon Protons{ $percentage|} ";
+ disconnected_format = "";
+ })
+ (lib.optionalAttrs config.my.home.bluetooth.enable {
+ block = "bluetooth";
+ mac = "88:C9:E8:6B:B7:55";
+ format = " $icon Quarks{ $percentage|} ";
+ disconnected_format = "";
+ })
+ (lib.optionalAttrs config.my.home.bluetooth.enable {
+ block = "bluetooth";
+ mac = "F7:78:BA:76:52:F7";
+ format = " $icon MX Ergo{ $percentage|} ";
+ disconnected_format = "";
+ })
+ {
+ block = "cpu";
+ }
+ {
+ block = "disk_space";
+ }
+ {
+ block = "net";
+ format = " $icon{| $ssid|} $ip{| $signal_strength|} ";
+ }
+ {
+ block = "backlight";
+ invert_icons = true;
+ }
+ {
+ block = "battery";
+ format = " $icon $percentage{ ($time)|} ";
+ empty_format = " $icon $percentage{ ($time)|} ";
+ not_charging_format = " $icon $percentage ";
+ full_format = " $icon $percentage ";
+ }
+ {
+ block = "temperature";
+ format_alt = " $icon ";
+ }
+ {
+ block = "sound";
+ device_kind = "source"; # Microphone status
+ format = " $icon ";
+ }
+ {
+ block = "sound";
+ show_volume_when_muted = true;
+ }
+ {
+ block = "time";
+ format = " $icon $timestamp.datetime(f:'%F %T') ";
+ interval = 5;
+ }
+ ];
+ };
+ };
+ };
+ };
+}
diff --git a/home/wm/rofi/default.nix b/home/wm/rofi/default.nix
new file mode 100644
index 0000000..9707ed7
--- /dev/null
+++ b/home/wm/rofi/default.nix
@@ -0,0 +1,21 @@
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.my.home.wm.rofi;
+in
+{
+ config = lib.mkIf cfg.enable {
+ programs.rofi = {
+ enable = true;
+
+ terminal = config.my.home.terminal.program; # null by default
+
+ package = pkgs.rofi.override {
+ plugins = with pkgs; [
+ rofi-emoji
+ ];
+ };
+
+ theme = "gruvbox-dark-hard";
+ };
+ };
+}
diff --git a/home/wm/screen-lock/default.nix b/home/wm/screen-lock/default.nix
new file mode 100644
index 0000000..3b2ead6
--- /dev/null
+++ b/home/wm/screen-lock/default.nix
@@ -0,0 +1,56 @@
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.my.home.wm.screen-lock;
+
+ notficationCmd =
+ let
+ duration = toString (cfg.notify.delay * 1000);
+ notifyCmd = "${lib.getExe pkgs.libnotify} -u critical -t ${duration}";
+ in
+ # Needs to be surrounded by quotes for systemd to launch it correctly
+ ''"${notifyCmd} -- 'Locking in ${toString cfg.notify.delay} seconds'"'';
+in
+{
+ config = lib.mkIf cfg.enable {
+ assertions = [
+ {
+ assertion =
+ let
+ inherit (cfg) cornerLock notify;
+ bothEnabled = cornerLock.enable && notify.enable;
+ cornerLockHigherThanNotify = cornerLock.delay >= notify.delay;
+ in
+ bothEnabled -> cornerLockHigherThanNotify;
+ message = ''
+ `config.my.home.wm.notify.delay` cannot have a value higher than
+ `config.my.home.wm.cornerLock.delay`.
+ '';
+ }
+ ];
+
+ services.screen-locker = {
+ enable = true;
+
+ inactiveInterval = cfg.timeout;
+
+ lockCmd = cfg.command;
+
+ xautolock = {
+ extraOptions = lib.optionals cfg.cornerLock.enable [
+ # Mouse corners: instant lock on upper-left, never lock on lower-right
+ "-cornerdelay"
+ "${toString cfg.cornerLock.delay}"
+ "-cornerredelay"
+ "${toString cfg.cornerLock.delay}"
+ "-corners"
+ "+00-"
+ ] ++ lib.optionals cfg.notify.enable [
+ "-notify"
+ "${toString cfg.notify.delay}"
+ "-notifier"
+ notficationCmd
+ ];
+ };
+ };
+ };
+}
diff --git a/home/x/default.nix b/home/x/default.nix
new file mode 100644
index 0000000..0312bc4
--- /dev/null
+++ b/home/x/default.nix
@@ -0,0 +1,21 @@
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.my.home.x;
+in
+{
+ imports = [
+ ./keyboard
+ ];
+
+ options.my.home.x = with lib; {
+ enable = mkEnableOption "X server configuration";
+ };
+
+ config = lib.mkIf cfg.enable {
+ xsession.enable = true;
+
+ home.packages = with pkgs; [
+ xsel
+ ];
+ };
+}
diff --git a/home/x/keyboard/default.nix b/home/x/keyboard/default.nix
new file mode 100644
index 0000000..40af800
--- /dev/null
+++ b/home/x/keyboard/default.nix
@@ -0,0 +1,12 @@
+{ config, lib, ... }:
+let
+ cfg = config.my.home.x;
+in
+{
+ config = lib.mkIf cfg.enable {
+ home.keyboard = {
+ layout = "fr";
+ variant = "us";
+ };
+ };
+}
diff --git a/home/xdg.nix b/home/xdg/default.nix
similarity index 75%
rename from home/xdg.nix
rename to home/xdg/default.nix
index bdeb326..3fd8dc9 100644
--- a/home/xdg.nix
+++ b/home/xdg/default.nix
@@ -3,8 +3,8 @@ let
cfg = config.my.home.xdg;
in
{
- options.my.home.xdg = with lib.my; {
- enable = mkDisableOption "XDG configuration";
+ options.my.home.xdg = with lib; {
+ enable = my.mkDisableOption "XDG configuration";
};
config.xdg = lib.mkIf cfg.enable {
@@ -31,18 +31,23 @@ in
# A tidy home is a tidy mind
dataFile = {
"bash/.keep".text = "";
+ "gdb/.keep".text = "";
"tig/.keep".text = "";
};
};
# I want a tidier home
config.home.sessionVariables = with config.xdg; lib.mkIf cfg.enable {
+ ANDROID_HOME = "${dataHome}/android";
CARGO_HOME = "${dataHome}/cargo";
DOCKER_CONFIG = "${configHome}/docker";
+ GDBHISTFILE = "${dataHome}/gdb/gdb_history";
HISTFILE = "${dataHome}/bash/history";
INPUTRC = "${configHome}/readline/inputrc";
LESSHISTFILE = "${dataHome}/less/history";
LESSKEY = "${configHome}/less/lesskey";
- WGETRC = "${configHome}/wgetrc";
+ PSQL_HISTORY = "${dataHome}/psql_history";
+ REDISCLI_HISTFILE = "${dataHome}/redis/rediscli_history";
+ XCOMPOSECACHE = "${dataHome}/X11/xcompose";
};
}
diff --git a/home/zathura/default.nix b/home/zathura/default.nix
new file mode 100644
index 0000000..6162542
--- /dev/null
+++ b/home/zathura/default.nix
@@ -0,0 +1,20 @@
+{ config, lib, ... }:
+let
+ cfg = config.my.home.zathura;
+in
+{
+ options.my.home.zathura = with lib; {
+ enable = mkEnableOption "zathura configuration";
+ };
+
+ config.programs.zathura = lib.mkIf cfg.enable {
+ enable = true;
+
+ options = {
+ # Show '~' instead of full path to '$HOME' in window title
+ "window-title-home-tilde" = true;
+ # Show '~' instead of full path to '$HOME' in status bar
+ "statusbar-home-tilde" = true;
+ };
+ };
+}
diff --git a/home/zsh/completion-styles.zsh b/home/zsh/completion-styles.zsh
index a0181a5..156bc2c 100644
--- a/home/zsh/completion-styles.zsh
+++ b/home/zsh/completion-styles.zsh
@@ -8,6 +8,8 @@ zstyle ':completion:*' menu select
zstyle ':completion:*' group-name ''
# Keep directories and files separated
zstyle ':completion:*' list-dirs-first true
+# Expand '//' to '/'
+zstyle ':completion:*' squeeze-slashes true
# Add colors to processes for kill completion
zstyle ':completion:*:*:kill:*:processes' list-colors '=(#b) #([0-9]#)*=0=01;31'
diff --git a/home/zsh/default.nix b/home/zsh/default.nix
index b7b9e8e..4cadb57 100644
--- a/home/zsh/default.nix
+++ b/home/zsh/default.nix
@@ -1,79 +1,95 @@
{ config, pkgs, lib, ... }:
let
cfg = config.my.home.zsh;
+
+ # Have a nice relative path for XDG_CONFIG_HOME, without leading `/`
+ relativeXdgConfig =
+ let
+ noHome = lib.removePrefix config.home.homeDirectory;
+ noSlash = lib.removePrefix "/";
+ in
+ noSlash (noHome config.xdg.configHome);
in
{
- options.my.home.zsh = with lib.my; {
- enable = mkDisableOption "zsh configuration";
+ options.my.home.zsh = with lib; {
+ enable = my.mkDisableOption "zsh configuration";
+
+ launchTmux = mkEnableOption "auto launch tmux at shell start";
};
- config.programs.zsh = lib.mkIf cfg.enable {
- enable = true;
- dotDir = ".config/zsh"; # Don't clutter $HOME
- enableCompletion = true;
-
- history = {
- size = 50000;
- ignoreSpace = true;
- ignoreDups = true;
- share = true;
- path = "${config.xdg.dataHome}/zsh/zsh_history";
- };
-
- plugins = with pkgs; [
- {
- name = "fast-syntax-highlighting";
- src = fetchFromGitHub {
- owner = "zdharma";
- repo = "fast-syntax-highlighting";
- rev = "v1.55";
- sha256 = "sha256-DWVFBoICroKaKgByLmDEo4O+xo6eA8YO792g8t8R7kA=";
- };
- }
- {
- name = "agkozak-zsh-prompt";
- src = fetchFromGitHub {
- owner = "agkozak";
- repo = "agkozak-zsh-prompt";
- rev = "v3.9.0";
- sha256 = "sha256-VTRL+8ph2eI7iPht15epkLggAgtLGxB3DORFTW5GrhE=";
- };
- }
+ config = lib.mkIf cfg.enable {
+ home.packages = with pkgs; [
+ zsh-completions
];
- # Modal editing is life, but CLI benefits from emacs gymnastics
- defaultKeymap = "emacs";
+ programs.zsh = {
+ enable = true;
+ dotDir = "${relativeXdgConfig}/zsh"; # Don't clutter $HOME
+ enableCompletion = true;
- initExtra = lib.concatMapStrings builtins.readFile [
- ./completion-styles.zsh
- ./extra-mappings.zsh
- ./options.zsh
- ];
+ history = {
+ size = 500000;
+ save = 500000;
+ extended = true;
+ expireDuplicatesFirst = true;
+ ignoreSpace = true;
+ ignoreDups = true;
+ share = false;
+ path = "${config.xdg.dataHome}/zsh/zsh_history";
+ };
- localVariables = {
- # I like having the full path
- AGKOZAK_PROMPT_DIRTRIM = 0;
- # Because I *am* from EPITA
- AGKOZAK_PROMPT_CHAR = [ "42sh$" "42sh#" ":" ];
- # Easy on the eyes
- AGKOZAK_COLORS_BRANCH_STATUS = "magenta";
- # I don't like moving my eyes
- AGKOZAK_LEFT_PROMPT_ONLY = 1;
+ plugins = [
+ {
+ name = "fast-syntax-highlighting";
+ file = "share/zsh/site-functions/fast-syntax-highlighting.plugin.zsh";
+ src = pkgs.zsh-fast-syntax-highlighting;
+ }
+ {
+ name = "agkozak-zsh-prompt";
+ file = "share/zsh/site-functions/agkozak-zsh-prompt.plugin.zsh";
+ src = pkgs.agkozak-zsh-prompt;
+ }
+ ];
+
+ # Modal editing is life, but CLI benefits from emacs gymnastics
+ defaultKeymap = "emacs";
+
+ # Make those happen early to avoid doing double the work
+ initExtraFirst = ''
+ ${
+ lib.optionalString cfg.launchTmux ''
+ # Launch tmux unless already inside one
+ if [ -z "$TMUX" ]; then
+ exec tmux new-session
+ fi
+ ''
+ }
+ '';
+
+ initExtra = ''
+ source ${./completion-styles.zsh}
+ source ${./extra-mappings.zsh}
+ source ${./options.zsh}
+
+ # Source local configuration
+ if [ -f "$ZDOTDIR/zshrc.local" ]; then
+ source "$ZDOTDIR/zshrc.local"
+ fi
+ '';
+
+ localVariables = {
+ # I like having the full path
+ AGKOZAK_PROMPT_DIRTRIM = 0;
+ # Because I *am* from EPITA
+ AGKOZAK_PROMPT_CHAR = [ "42sh$" "42sh#" ":" ];
+ # Easy on the eyes
+ AGKOZAK_COLORS_BRANCH_STATUS = "magenta";
+ # I don't like moving my eyes
+ AGKOZAK_LEFT_PROMPT_ONLY = 1;
+ };
+
+ # Enable VTE integration
+ enableVteIntegration = true;
};
-
- shellAliases = {
- # Sometime `gpg-agent` errors out...
- reset-agent = "gpg-connect-agent updatestartuptty /bye";
- };
- };
-
- # Fuzzy-wuzzy
- config.programs.fzf = lib.mkIf cfg.enable {
- enable = true;
- enableZshIntegration = true;
- };
-
- config.programs.dircolors = lib.mkIf cfg.enable {
- enable = true;
};
}
diff --git a/home/zsh/extra-mappings.zsh b/home/zsh/extra-mappings.zsh
index abd6e58..3456e13 100644
--- a/home/zsh/extra-mappings.zsh
+++ b/home/zsh/extra-mappings.zsh
@@ -1,8 +1,7 @@
-# Fix delete key not working
-bindkey "\e[3~" delete-char
+# shellcheck disable=2154
# Fix Ctrl+u killing from the cursor instead of the whole line
-bindkey \^U backward-kill-line
+bindkey '^u' backward-kill-line
# Use Ctrl+x-(Ctrl+)e to edit the current command line in VISUAL/EDITOR
autoload -U edit-command-line
@@ -10,5 +9,124 @@ zle -N edit-command-line
bindkey '^xe' edit-command-line
bindkey '^x^e' edit-command-line
+# The expression: (( ${+terminfo} )) should never fail, but does if we
+# don't have a tty, perhaps due to a bug in the zsh/terminfo module.
+if ! { [ "$TERM" != emacs ] && (( ${+terminfo} )) 2>/dev/null; }; then
+ return
+fi
+
+# Make sure that the terminal is in application mode when zle is active, since
+# only then values from $terminfo are valid
+if (( ${+terminfo[smkx]} )) && (( ${+terminfo[rmkx]} )); then
+ autoload -Uz add-zle-hook-widget
+
+ zle_application_mode_start() { echoti smkx; }
+ zle_application_mode_stop() { echoti rmkx; }
+
+ add-zle-hook-widget -Uz zle-line-init zle_application_mode_start
+ add-zle-hook-widget -Uz zle-line-finish zle_application_mode_stop
+fi
+
+
+# Fix delete key not working
+if [ -n "${terminfo[kdch1]}" ]; then
+ bindkey -M emacs "${terminfo[kdch1]}" delete-char
+ bindkey -M viins "${terminfo[kdch1]}" delete-char
+ bindkey -M vicmd "${terminfo[kdch1]}" delete-char
+else
+ bindkey -M emacs "^[[3~" delete-char
+ bindkey -M viins "^[[3~" delete-char
+ bindkey -M vicmd "^[[3~" delete-char
+
+ bindkey -M emacs "^[3;5~" delete-char
+ bindkey -M viins "^[3;5~" delete-char
+ bindkey -M vicmd "^[3;5~" delete-char
+fi
+
+# Ctrl-Delete to delete a whole word forward
+if [ -n "${terminfo[kdl1]}" ]; then
+ bindkey -M emacs "${terminfo[kdl1]}" kill-word
+ bindkey -M viins "${terminfo[kdl1]}" kill-word
+ bindkey -M vicmd "${terminfo[kdl1]}" kill-word
+else
+ bindkey -M emacs '^[[3;5~' kill-word
+ bindkey -M viins '^[[3;5~' kill-word
+ bindkey -M vicmd '^[[3;5~' kill-word
+fi
+
# Enable Shift-Tab to go backwards in completion list
-bindkey '^[[Z' reverse-menu-complete
+if [ -n "${terminfo[kcbt]}" ]; then
+ bindkey -M emacs "${terminfo[kcbt]}" reverse-menu-complete
+ bindkey -M viins "${terminfo[kcbt]}" reverse-menu-complete
+ bindkey -M vicmd "${terminfo[kcbt]}" reverse-menu-complete
+else
+ bindkey -M emacs '^[[Z' reverse-menu-complete
+ bindkey -M viins '^[[Z' reverse-menu-complete
+ bindkey -M vicmd '^[[Z' reverse-menu-complete
+fi
+
+# Ctrl-Left moves backward one word
+if [ -n "${terminfo[kLFT5]}" ]; then
+ bindkey -M emacs "${terminfo[kLFT5]}" backward-word
+ bindkey -M viins "${terminfo[kLFT5]}" backward-word
+ bindkey -M vicmd "${terminfo[kLFT5]}" backward-word
+else
+ bindkey -M emacs '^[[1;5D' backward-word
+ bindkey -M viins '^[[1;5D' backward-word
+ bindkey -M vicmd '^[[1;5D' backward-word
+fi
+
+# Ctrl-Right moves forward one word
+if [ -n "${terminfo[kRIT5]}" ]; then
+ bindkey -M emacs "${terminfo[kRIT5]}" forward-word
+ bindkey -M viins "${terminfo[kRIT5]}" forward-word
+ bindkey -M vicmd "${terminfo[kRIT5]}" forward-word
+else
+ bindkey -M emacs '^[[1;5C' forward-word
+ bindkey -M viins '^[[1;5C' forward-word
+ bindkey -M vicmd '^[[1;5C' forward-word
+fi
+
+# PageUp goes backwards in history
+if [ -n "${terminfo[kpp]}" ]; then
+ bindkey -M emacs "${terminfo[kpp]}" up-line-or-history
+ bindkey -M viins "${terminfo[kpp]}" up-line-or-history
+ bindkey -M vicmd "${terminfo[kpp]}" up-line-or-history
+else
+ bindkey -M emacs "^[[5~" up-line-or-history
+ bindkey -M viins "^[[5~" up-line-or-history
+ bindkey -M vicmd "^[[5~" up-line-or-history
+fi
+
+# PageDown goes forward in history
+if [ -n "${terminfo[knp]}" ]; then
+ bindkey -M emacs "${terminfo[knp]}" down-line-or-history
+ bindkey -M viins "${terminfo[knp]}" down-line-or-history
+ bindkey -M vicmd "${terminfo[knp]}" down-line-or-history
+else
+ bindkey -M emacs "^[[6~" down-line-or-history
+ bindkey -M viins "^[[6~" down-line-or-history
+ bindkey -M vicmd "^[[6~" down-line-or-history
+fi
+
+# Home goes to the beginning of the line
+if [ -n "${terminfo[khome]}" ]; then
+ bindkey -M emacs "${terminfo[khome]}" beginning-of-line
+ bindkey -M viins "${terminfo[khome]}" beginning-of-line
+ bindkey -M vicmd "${terminfo[khome]}" beginning-of-line
+else
+ bindkey -M emacs "^[[1~" beginning-of-line
+ bindkey -M viins "^[[1~" beginning-of-line
+ bindkey -M vicmd "^[[1~" beginning-of-line
+fi
+
+# End goes to the end of the line
+if [ -n "${terminfo[kend]}" ]; then
+ bindkey -M emacs "${terminfo[kend]}" end-of-line
+ bindkey -M viins "${terminfo[kend]}" end-of-line
+ bindkey -M vicmd "${terminfo[kend]}" end-of-line
+else
+ bindkey -M emacs "^[[4~" end-of-line
+ bindkey -M viins "^[[4~" end-of-line
+ bindkey -M vicmd "^[[4~" end-of-line
+fi
diff --git a/home/zsh/options.zsh b/home/zsh/options.zsh
index b02ca54..82047ff 100644
--- a/home/zsh/options.zsh
+++ b/home/zsh/options.zsh
@@ -1,12 +1,18 @@
# Show an error when a globbing expansion doesn't find any match
setopt nomatch
# List on ambiguous completion and Insert first match immediately
-setopt autolist menucomplete
+setopt auto_list menu_complete
# Use pushd when cd-ing around
-setopt autopushd pushdminus pushdsilent
+setopt auto_pushd pushd_minus pushd_silent
# Use single quotes in string without the weird escape tricks
-setopt rcquotes
+setopt rc_quotes
# Single word commands can resume an existing job
-setopt autoresume
+setopt auto_resume
+# Show history expansion before running a command
+setopt hist_verify
+# Append commands to history as they are exectuted
+setopt inc_append_history_time
+# Remove useless whitespace from commands
+setopt hist_reduce_blanks
# Those options aren't wanted
-unsetopt beep extendedglob notify
+unsetopt beep extended_glob notify
diff --git a/hosts/homes/ambroisie/default.nix b/hosts/homes/ambroisie/default.nix
new file mode 100644
index 0000000..42ea5b8
--- /dev/null
+++ b/hosts/homes/ambroisie/default.nix
@@ -0,0 +1,5 @@
+# Default home-manager configuration
+{ ... }:
+{
+ # Default configuration, nothing to do
+}
diff --git a/hosts/homes/ambroisie@ambroisie/default.nix b/hosts/homes/ambroisie@ambroisie/default.nix
new file mode 100644
index 0000000..94efc09
--- /dev/null
+++ b/hosts/homes/ambroisie@ambroisie/default.nix
@@ -0,0 +1,17 @@
+# Google Cloudtop configuration
+{ lib, pkgs, ... }:
+{
+ # Google specific configuration
+ home.homeDirectory = "/usr/local/google/home/ambroisie";
+
+ # Some tooling (e.g: SSH) need to use this library
+ home.sessionVariables = {
+ LD_PRELOAD = "/lib/x86_64-linux-gnu/libnss_cache.so.2\${LD_PRELOAD:+:}$LD_PRELOAD";
+ };
+
+ systemd.user.sessionVariables = {
+ LD_PRELOAD = "/lib/x86_64-linux-gnu/libnss_cache.so.2\${LD_PRELOAD:+:}$LD_PRELOAD";
+ };
+
+ programs.git.package = lib.mkForce pkgs.emptyDirectory;
+}
diff --git a/hosts/nixos/aramis/boot.nix b/hosts/nixos/aramis/boot.nix
new file mode 100644
index 0000000..2169da5
--- /dev/null
+++ b/hosts/nixos/aramis/boot.nix
@@ -0,0 +1,32 @@
+{ ... }:
+{
+ boot = {
+ loader = {
+ systemd-boot.enable = true;
+ efi.canTouchEfiVariables = true;
+ };
+
+ initrd = {
+ availableKernelModules = [
+ "nvme"
+ "sd_mod"
+ "sdhci_pci"
+ "usb_storage"
+ "usbhid"
+ "xhci_pci"
+ ];
+ kernelModules = [
+ "dm-snapshot"
+ ];
+ luks.devices.crypt = {
+ device = "/dev/nvme0n1p1";
+ preLVM = true;
+ };
+ };
+
+ kernelModules = [
+ "kvm-intel"
+ ];
+ extraModulePackages = [ ];
+ };
+}
diff --git a/hosts/nixos/aramis/default.nix b/hosts/nixos/aramis/default.nix
new file mode 100644
index 0000000..c72fb11
--- /dev/null
+++ b/hosts/nixos/aramis/default.nix
@@ -0,0 +1,30 @@
+# Edit this configuration file to define what should be installed on
+# your system. Help is available in the configuration.nix(5) man page
+# and in the NixOS manual (accessible by running ‘nixos-help’).
+
+{ ... }:
+
+{
+ imports = [
+ ./boot.nix
+ ./hardware.nix
+ ./home.nix
+ ./networking.nix
+ ./profiles.nix
+ ./programs.nix
+ ./secrets
+ ./services.nix
+ ./sound.nix
+ ];
+
+ # Set your time zone.
+ time.timeZone = "Europe/London";
+
+ # This value determines the NixOS release from which the default
+ # settings for stateful data, like file locations and database versions
+ # on your system were taken. It‘s perfectly fine and recommended to leave
+ # this value at the release version of the first install of this system.
+ # Before changing this value read the documentation for this option
+ # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
+ system.stateVersion = "20.09"; # Did you read the comment?
+}
diff --git a/hosts/nixos/aramis/hardware.nix b/hosts/nixos/aramis/hardware.nix
new file mode 100644
index 0000000..c66b426
--- /dev/null
+++ b/hosts/nixos/aramis/hardware.nix
@@ -0,0 +1,40 @@
+{ lib, modulesPath, ... }:
+{
+ imports = [
+ (modulesPath + "/installer/scan/not-detected.nix")
+ ];
+
+ fileSystems = {
+ "/" = {
+ device = "/dev/disk/by-label/nixos";
+ fsType = "ext4";
+ };
+
+ "/boot" = {
+ device = "/dev/disk/by-label/boot";
+ fsType = "vfat";
+ };
+ };
+
+ swapDevices = [
+ { device = "/dev/disk/by-label/swap"; }
+ ];
+
+ powerManagement.cpuFreqGovernor = lib.mkDefault "powersave";
+
+ my.hardware = {
+ firmware = {
+ cpuFlavor = "intel";
+ };
+ };
+
+ hardware = {
+ trackpoint = {
+ enable = true;
+
+ emulateWheel = true; # Holding middle buttons allows scrolling
+
+ device = "TPPS/2 Elan TrackPoint"; # Use the correct device name
+ };
+ };
+}
diff --git a/hosts/nixos/aramis/home.nix b/hosts/nixos/aramis/home.nix
new file mode 100644
index 0000000..66a0892
--- /dev/null
+++ b/hosts/nixos/aramis/home.nix
@@ -0,0 +1,34 @@
+{ pkgs, ... }:
+{
+ my.home = {
+ # Use graphical pinentry
+ bitwarden.pinentry = "gtk2";
+ # Ebook library
+ calibre.enable = true;
+ # Some amount of social life
+ discord.enable = true;
+ # Image viewver
+ feh.enable = true;
+ # Firefo profile and extensions
+ firefox.enable = true;
+ # Blue light filter
+ gammastep.enable = true;
+ # Use a small popup to enter passwords
+ gpg.pinentry = "gtk2";
+ # Machine specific packages
+ packages.additionalPackages = with pkgs; [
+ element-desktop # Matrix client
+ jellyfin-media-player # Wraps the webui and mpv together
+ pavucontrol # Audio mixer GUI
+ transgui # Transmission remote
+ ];
+ # Minimal video player
+ mpv.enable = true;
+ # Network-Manager applet
+ nm-applet.enable = true;
+ # Terminal
+ terminal.program = "alacritty";
+ # Zathura document viewer
+ zathura.enable = true;
+ };
+}
diff --git a/hosts/nixos/aramis/install.sh b/hosts/nixos/aramis/install.sh
new file mode 100755
index 0000000..b03a6df
--- /dev/null
+++ b/hosts/nixos/aramis/install.sh
@@ -0,0 +1,53 @@
+#!/bin/sh
+
+set -eu
+
+if [ "$(id -u)" -ne 0 ]; then
+ echo "This script must be run as root" >&2
+ exit 1
+fi
+
+SWAP_SIZE=16GiB
+
+parted /dev/nvme0n1 --script -- \
+ mklabel gpt \
+ mkpart primary 512MiB 100% \
+ mkpart ESP fat32 1MiB 512MiB \
+ set 2 esp on
+
+cryptsetup luksFormat /dev/nvme0n1p1
+cryptsetup open /dev/nvme0n1p1 crypt
+
+pvcreate /dev/mapper/crypt
+vgcreate lvm /dev/mapper/crypt
+lvcreate -L "$SWAP_SIZE" -n swap lvm
+lvcreate -l 100%FREE -n root lvm
+
+mkfs.ext4 -L nixos /dev/lvm/root
+mkswap -L swap /dev/lvm/swap
+mkfs.vfat -n boot /dev/nvme0n1p2
+
+mount /dev/disk/by-label/nixos /mnt
+mkdir /mnt/boot
+mount /dev/nvme0n1p2 /mnt/boot
+swapon /dev/lvm/swap
+
+cat << EOF
+# Run the following commands as setup user
+nixos-generate-config --root /mnt
+
+# Change uuids to labels
+vim /mnt/etc/nixos/hardware-configuration.nix
+
+# Install system
+mkdir -p /mnt/home/ambroisie/git/nix/config
+cd /mnt/home/ambroisie/git/nix/config
+
+git clone .
+# Assuming you set up GPG key correctly
+git crypt unlock
+
+# Setup LUKS with 'boot.initrd.luks.devices.crypt', device is /dev/nvme0n1p1, preLVM = true
+
+# Use 'nixos-install --flake .#aramis --root /mnt --impure' because of home-manager issue
+EOF
diff --git a/hosts/nixos/aramis/networking.nix b/hosts/nixos/aramis/networking.nix
new file mode 100644
index 0000000..fbf4c6b
--- /dev/null
+++ b/hosts/nixos/aramis/networking.nix
@@ -0,0 +1,19 @@
+{ ... }:
+{
+ networking = {
+ hostName = "aramis";
+
+ # The global useDHCP flag is deprecated, therefore explicitly set to false here.
+ # Per-interface useDHCP will be mandatory in the future, so this generated config
+ # replicates the default behaviour.
+ useDHCP = false;
+ };
+
+ my.hardware.networking = {
+ # Which interface is used to connect to the internet
+ externalInterface = "enp0s3";
+
+ # Enable WiFi integration
+ wireless.enable = true;
+ };
+}
diff --git a/hosts/nixos/aramis/profiles.nix b/hosts/nixos/aramis/profiles.nix
new file mode 100644
index 0000000..d86da5a
--- /dev/null
+++ b/hosts/nixos/aramis/profiles.nix
@@ -0,0 +1,19 @@
+{ ... }:
+{
+ my.profiles = {
+ # Bluetooth configuration and GUI
+ bluetooth.enable = true;
+ # Mouse and keyboard configuration
+ devices.enable = true;
+ # GTK theme configuration
+ gtk.enable = true;
+ # Laptop specific configuration
+ laptop.enable = true;
+ # Printers are hell, but so is the unability to print
+ printing.enable = true;
+ # i3 configuration
+ wm.windowManager = "i3";
+ # X configuration
+ x.enable = true;
+ };
+}
diff --git a/hosts/nixos/aramis/programs.nix b/hosts/nixos/aramis/programs.nix
new file mode 100644
index 0000000..426ca2a
--- /dev/null
+++ b/hosts/nixos/aramis/programs.nix
@@ -0,0 +1,7 @@
+{ ... }:
+{
+ my.programs = {
+ # Steam configuration
+ steam.enable = true;
+ };
+}
diff --git a/hosts/nixos/aramis/secrets/default.nix b/hosts/nixos/aramis/secrets/default.nix
new file mode 100644
index 0000000..c6f02dc
--- /dev/null
+++ b/hosts/nixos/aramis/secrets/default.nix
@@ -0,0 +1,25 @@
+{ config, lib, ... }:
+
+{
+ config.age = {
+ secrets =
+ let
+ toName = lib.removeSuffix ".age";
+ userExists = u: builtins.hasAttr u config.users.users;
+ # Only set the user if it exists, to avoid warnings
+ userIfExists = u: if userExists u then u else "root";
+ toSecret = name: { owner ? "root", ... }: {
+ file = ./. + "/${name}";
+ owner = lib.mkDefault (userIfExists owner);
+ };
+ convertSecrets = n: v: lib.nameValuePair (toName n) (toSecret n v);
+ secrets = import ./secrets.nix;
+ in
+ lib.mapAttrs' convertSecrets secrets;
+
+ identityPaths = [
+ # Due to being a laptop, this host does not itself have any SSH keys
+ "/home/ambroisie/.ssh/agenix"
+ ];
+ };
+}
diff --git a/hosts/nixos/aramis/secrets/secrets.nix b/hosts/nixos/aramis/secrets/secrets.nix
new file mode 100644
index 0000000..ce159a5
--- /dev/null
+++ b/hosts/nixos/aramis/secrets/secrets.nix
@@ -0,0 +1,13 @@
+# Host-specific secrets
+let
+ keys = import ../../../../keys;
+
+ all = [
+ # This host is a laptop, it does not have a host key
+ # Allow me to modify the secrets anywhere
+ keys.users.ambroisie
+ ];
+in
+{
+ "wireguard/private-key.age".publicKeys = all;
+}
diff --git a/hosts/nixos/aramis/secrets/wireguard/private-key.age b/hosts/nixos/aramis/secrets/wireguard/private-key.age
new file mode 100644
index 0000000..d790b64
Binary files /dev/null and b/hosts/nixos/aramis/secrets/wireguard/private-key.age differ
diff --git a/hosts/nixos/aramis/services.nix b/hosts/nixos/aramis/services.nix
new file mode 100644
index 0000000..0287c30
--- /dev/null
+++ b/hosts/nixos/aramis/services.nix
@@ -0,0 +1,8 @@
+{ ... }:
+{
+ config.my.services = {
+ wireguard = {
+ enable = true;
+ };
+ };
+}
diff --git a/hosts/nixos/aramis/sound.nix b/hosts/nixos/aramis/sound.nix
new file mode 100644
index 0000000..41ff7f7
--- /dev/null
+++ b/hosts/nixos/aramis/sound.nix
@@ -0,0 +1,8 @@
+{ ... }:
+{
+ my.hardware.sound = {
+ pipewire = {
+ enable = true;
+ };
+ };
+}
diff --git a/machines/porthos/boot.nix b/hosts/nixos/porthos/boot.nix
similarity index 84%
rename from machines/porthos/boot.nix
rename to hosts/nixos/porthos/boot.nix
index 3b56eb9..fbc5db7 100644
--- a/machines/porthos/boot.nix
+++ b/hosts/nixos/porthos/boot.nix
@@ -6,9 +6,8 @@
# Use the GRUB 2 boot loader.
loader.grub = {
enable = true;
- version = 2;
# Define on which hard drive you want to install Grub.
- device = "/dev/sda";
+ device = "/dev/disk/by-id/ata-HGST_HUS724020ALA640_PN2181P6J58M1P";
};
initrd = {
diff --git a/porthos.nix b/hosts/nixos/porthos/default.nix
similarity index 73%
rename from porthos.nix
rename to hosts/nixos/porthos/default.nix
index ce3a200..326d1cd 100644
--- a/porthos.nix
+++ b/hosts/nixos/porthos/default.nix
@@ -1,18 +1,20 @@
-# Porthos self-hosted server
+# Porthos specific settings
{ ... }:
{
imports = [
- # Include generic settings
- ./modules
- # Include porthos-specific modules
- ./machines/porthos
- # Include my secrets
+ ./boot.nix
+ ./hardware.nix
+ ./home.nix
+ ./networking.nix
./secrets
- # Include my services
- ./services
+ ./services.nix
+ ./users.nix
];
+ # Set your time zone.
+ time.timeZone = "Europe/Paris";
+
# This value determines the NixOS release from which the default
# settings for stateful data, like file locations and database versions
# on your system were taken. It‘s perfectly fine and recommended to leave
diff --git a/machines/porthos/hardware.nix b/hosts/nixos/porthos/hardware.nix
similarity index 100%
rename from machines/porthos/hardware.nix
rename to hosts/nixos/porthos/hardware.nix
diff --git a/hosts/nixos/porthos/home.nix b/hosts/nixos/porthos/home.nix
new file mode 100644
index 0000000..53d5d25
--- /dev/null
+++ b/hosts/nixos/porthos/home.nix
@@ -0,0 +1,7 @@
+{ ... }:
+{
+ my.home = {
+ # Always start a tmux session when opening a shell session
+ zsh.launchTmux = true;
+ };
+}
diff --git a/machines/porthos/install.sh b/hosts/nixos/porthos/install.sh
similarity index 95%
rename from machines/porthos/install.sh
rename to hosts/nixos/porthos/install.sh
index 44ea787..de87aa7 100644
--- a/machines/porthos/install.sh
+++ b/hosts/nixos/porthos/install.sh
@@ -46,7 +46,7 @@ vim /mnt/etc/nixos/hardware-configuration.nix
mkdir -p /mnt/home/ambroisie/git/nix/config
cd /mnt/home/ambroisie/git/nix/config
-nix-env -iA nixos.git nixos.nixFlakes nixos.git-crypt
+nix-env -iA nixos.git nixos.nix nixos.git-crypt
git clone .
# Assuming you set up GPG key correctly
git crypt unlock
diff --git a/machines/porthos/networking.nix b/hosts/nixos/porthos/networking.nix
similarity index 94%
rename from machines/porthos/networking.nix
rename to hosts/nixos/porthos/networking.nix
index e593eeb..1e2c9cd 100644
--- a/machines/porthos/networking.nix
+++ b/hosts/nixos/porthos/networking.nix
@@ -31,5 +31,5 @@
};
# Which interface is used to connect to the internet
- my.networking.externalInterface = "eth0";
+ my.hardware.networking.externalInterface = "eth0";
}
diff --git a/hosts/nixos/porthos/secrets/acme/dns-key.age b/hosts/nixos/porthos/secrets/acme/dns-key.age
new file mode 100644
index 0000000..97d397c
--- /dev/null
+++ b/hosts/nixos/porthos/secrets/acme/dns-key.age
@@ -0,0 +1,10 @@
+age-encryption.org/v1
+-> ssh-ed25519 cKojmg 0bz3W8QcGaulxy+kDmM717jTthQpFOCwV9HkenFJEyo
+NKeh1/JkX4WAWbOjUeKLMbsyCevnDf3a70FfYUav26c
+-> ssh-ed25519 jPowng Q59ybJMMteOSB6hZ5m6UPP0N2p8jrDSu5vBYwPgGcRw
+j420on2jSsfMsv4MDtiOTMIFjaXV7sIsrS+g4iab+68
+-> z}.q-grease s2W ssh-ed25519 cKojmg YlDuj9wwBKSHHvQOhfti1ah95vxDV3bLE+GElBkyTB0
+KsMyd3L4GaQa0eDQps+bJXj+cpy0zUNvFXU8NAmtThI
+-> ssh-ed25519 jPowng JB4UtNyZab4ab4Pep3acyMjwCbluuEPuI6YOQ/045Fo
+P9qnrPDGpHJL1TyNqYdNfqkd21Yjn/5mlovorWy60j4
+-> _6l|s-grease M ]2qMsa'w P] j0EE
+W3CToUTg
+--- 8aWYUi33mEIKFcFbphlDZumnBu9Xbj+j18dQbElx1v8
+3$m(øäÂTK±î·”eAZâ>dn:-Òí‚¥ˆÅh.›(¶U²!rìx D3ô‡4Ø93~È»f{üƒšL¸ÎþÆ£ÃØ>Þß^v›l—¡Î-=„í¯ä£ÉU'â»(,µ#;¤ªHñÆ@M%|ʦ
\ No newline at end of file
diff --git a/hosts/nixos/porthos/secrets/backup/password.age b/hosts/nixos/porthos/secrets/backup/password.age
new file mode 100644
index 0000000..3af9fbe
--- /dev/null
+++ b/hosts/nixos/porthos/secrets/backup/password.age
@@ -0,0 +1,8 @@
+age-encryption.org/v1
+-> ssh-ed25519 cKojmg dgS4bezgtDi44R1A8am+J6zh80kUVYTo1heaxJCtzX4
+F3w/62xwtqYa40NU7OvF9pnZzYz/5hACAGJfMA4e2zw
+-> ssh-ed25519 jPowng lx81CK3yeNp9RjHCUFJeKYZlRzxBmXuADVBvRc13zCI
+P7e75t8xU+ZkYmeQ8mmMfyZZsRdG1J8yrvSUkiWzkFQ
+-> *z4/`-grease S/)a{e sFd";=
+--- 15FVhqRTkoPFEeETRRyFQhsv4Fn19Ozlax0u8Zy9mNA
+õ#+¥àÎvøSÈ4èá}§Rì%‹Î¯F4fnDœ˜J¹¤Z‹¸A¥Û™,_
\ No newline at end of file
diff --git a/hosts/nixos/porthos/secrets/default.nix b/hosts/nixos/porthos/secrets/default.nix
new file mode 100644
index 0000000..83af695
--- /dev/null
+++ b/hosts/nixos/porthos/secrets/default.nix
@@ -0,0 +1,20 @@
+{ config, lib, ... }:
+
+{
+ config.age = {
+ secrets =
+ let
+ toName = lib.removeSuffix ".age";
+ userExists = u: builtins.hasAttr u config.users.users;
+ # Only set the user if it exists, to avoid warnings
+ userIfExists = u: if userExists u then u else "root";
+ toSecret = name: { owner ? "root", ... }: {
+ file = ./. + "/${name}";
+ owner = lib.mkDefault (userIfExists owner);
+ };
+ convertSecrets = n: v: lib.nameValuePair (toName n) (toSecret n v);
+ secrets = import ./secrets.nix;
+ in
+ lib.mapAttrs' convertSecrets secrets;
+ };
+}
diff --git a/hosts/nixos/porthos/secrets/drone/gitea.age b/hosts/nixos/porthos/secrets/drone/gitea.age
new file mode 100644
index 0000000..90ff83b
--- /dev/null
+++ b/hosts/nixos/porthos/secrets/drone/gitea.age
@@ -0,0 +1,10 @@
+age-encryption.org/v1
+-> ssh-ed25519 jPowng 3HjtINU02yfyvpaoTtCG/G3cgymFvKWf3qf/grw/+24
+G+KnHtrt5A3U1ICTpSceoE+FJSj6hAb4mjp2DkfDWr0
+-> ssh-ed25519 cKojmg CXrsCxJxWNIhvvwYjZ1rrSYTwLNMR3kWdPk5ExHEfFA
+OJi3tFcnpdGvxc8ETcGt4lbFJiUU+lR3AqN4Y8PItDk
+-> p-grease 7 AQO{DHzj j$B&+ dc
+Q7zhy6hOfkEh6XgYNHQrH3zma1BLxnEKDopPAOnBoPFRfi7c
+--- ZsL5zf7/jJ6yPor9j1V0iV/bXxgAsxlXsxDYwCqrLSk
+AO&âz€?‹;\1¥f*='ë³!.óVí·”VÀÜ=ιªø÷vzÛY"P ²Ê;°ò)¾òK0ëÞBs}hR=öOмç*îMÿ2oœý
\ No newline at end of file
diff --git a/hosts/nixos/porthos/secrets/drone/secret.age b/hosts/nixos/porthos/secrets/drone/secret.age
new file mode 100644
index 0000000..c529200
--- /dev/null
+++ b/hosts/nixos/porthos/secrets/drone/secret.age
@@ -0,0 +1,9 @@
+age-encryption.org/v1
+-> ssh-ed25519 cKojmg 1+cLlzctgcM0FnVDwMPOAqBkvMcDBRg8SvCw4djI93Y
+oV2XI4f1AvM9P591kZZ6NgJXa+SDtqGzCSgc4psOmxM
+-> ssh-ed25519 jPowng Ufjfh1p350XxRPg95+/DHdmnl4lC0bbzUUlaxd1Bmxc
+/RHwFDSn2ov+60r1uHUigrsn99+GmmKmlk4h4T2gbA0
+-> *Lc$@-grease
+pzVJAHy1qRq3jUrnFV0DDO7/hwV1US4Ogf0RsrVfX0xzbr73uJ003YjieVB25LqN
+--- ME7/iVevyiguyhXugbkVFGzJV0yDccyKNlWbEZa/FmY
+YžŠXjb2uþnd;i0íýX]…§é0–þjé’L„PÔT~óú ƒÙ^kc”$D×ÚÛr¹úu³¶fr€e¸OÕ¸þ+p•¨&ãw®öϨ
\ No newline at end of file
diff --git a/hosts/nixos/porthos/secrets/drone/ssh/private-key.age b/hosts/nixos/porthos/secrets/drone/ssh/private-key.age
new file mode 100644
index 0000000..0211701
Binary files /dev/null and b/hosts/nixos/porthos/secrets/drone/ssh/private-key.age differ
diff --git a/hosts/nixos/porthos/secrets/gitea/mail-password.age b/hosts/nixos/porthos/secrets/gitea/mail-password.age
new file mode 100644
index 0000000..915f8e9
--- /dev/null
+++ b/hosts/nixos/porthos/secrets/gitea/mail-password.age
@@ -0,0 +1,9 @@
+age-encryption.org/v1
+-> ssh-ed25519 jPowng BkIjie2KrwDLaZYYIguCs7TPA/wQy+YPguikuhfye0M
+7viTA/EGYB/jRKQm6fFd86DMd4j+Jxsaw/xQ1T8ZKNo
+-> ssh-ed25519 cKojmg t1Y8bZvPccNAX8vWQLTfCyOJIBXN515vyfFrEI2EVww
+bJEjpIWrKeQrA/JfY7FRdB6hpHwR/aG4Vya1ChFNBKs
+-> jK/-grease Oz.R ?;)G ],
+AuHk9TcC9kl0dg8/L6UfHIk3e9fgGwSTJAJpVgInhok
+--- 47z9lol5MtpX0IsO/0ggLDMcNVfl4lNNvoHUSwOU/18
+)gЪeuÞ!œš-
ÞTì¥YAðM+ˆãGbMe@|A,è&ãÆE!܆p=P²=û9¹ÙP¹!Üö’Q|Ðä r
\ No newline at end of file
diff --git a/hosts/nixos/porthos/secrets/lohr/secret.age b/hosts/nixos/porthos/secrets/lohr/secret.age
new file mode 100644
index 0000000..fa310b4
--- /dev/null
+++ b/hosts/nixos/porthos/secrets/lohr/secret.age
@@ -0,0 +1,9 @@
+age-encryption.org/v1
+-> ssh-ed25519 cKojmg HCVbkI26JjkBgm1L2cpunVui0PfHLNfnx6VczErF3A4
+3jEHfT6wUqNNFZFaVeiNBUhSKZmuKclPmubDMsda5O8
+-> ssh-ed25519 jPowng SyClv9kGtjRKSXdig27tiqp66wD1T8QsHeOD2JQl4QA
+8zdtfSJEh5/bfu5tb6M8Jgy5CZPiWD8TLQDpzp6cTr0
+-> 3r2-grease
+Lg/G911eZjeZTw5xhqje26vDfJkcSro+gKQ5SUboxLMnaibNi1qTeRLR
+--- Q5/fikhVPoK+NFujTso5V7cty4k/dQlzFlz5z9DkzYk
+øt/ŒW‹AMuˆ"Þêð´—ó-!@›¨E1¯” äR[eŽ’hÖû3
ëŒÉÐScoÝBt1TýØb¨äÀ3möP×Tc¤feP
\ No newline at end of file
diff --git a/hosts/nixos/porthos/secrets/lohr/ssh-key.age b/hosts/nixos/porthos/secrets/lohr/ssh-key.age
new file mode 100644
index 0000000..30a5e25
Binary files /dev/null and b/hosts/nixos/porthos/secrets/lohr/ssh-key.age differ
diff --git a/hosts/nixos/porthos/secrets/matrix/mail.age b/hosts/nixos/porthos/secrets/matrix/mail.age
new file mode 100644
index 0000000..1fe3a71
--- /dev/null
+++ b/hosts/nixos/porthos/secrets/matrix/mail.age
@@ -0,0 +1,9 @@
+age-encryption.org/v1
+-> ssh-ed25519 cKojmg lmu3MinmydRHD0A/YVRRtopermfoBC8M8cTHfVanY1s
+ygrtpZZJ7aeQTblNazpoP7DdifmDxHsE3DFJsIrWX5M
+-> ssh-ed25519 jPowng X0cihOc+fBtmtrkEivIHQngdYIobezXEF1x+pHqNzAw
+/+sw9x1NWY0anZhDMpAywBPrR0F4XCHaF9e8j/Yo/kI
+-> 32;%1s-grease
+JafjuSZty6a4NSO/y4y5wHWL8Mw
+--- dwCl66vdpsL0MR5NWWvg3JUnQ2QZQBeW0Dj0l5tvOKY
+oi,`ÓÜ#uÄwW%PoubÚcy8ó
ƒÃÉ><¿F‰Ååq…ÂKÂÇk0Çk/hÀ¥Ÿ5势ÝF+ýu‡ •e€¾Ÿ²óôbãè>1QŠ2®ñwn˜WbÖ–B˜âîiŸ^xurâ†-/llùÒÀÀ-ã=°7;jã0»I×%Fi¼í€ø‹™A;Y†ìUd]KÅI0(½ ”øAg£Ðóž^†uG:äpkJ’Ÿ:q¢šWSaLw¯¿Ô!ïM³4ã L/ùZŇ®¢D¶-XéUb»‘vÊbP‚ó›0ÇÅfÂ9êú †âJ`ÃX°ôÐOÅ!s›{ÙÄQAšc€c;ÏÃÑ‹4öMíچݹlxH&ïéöé{é}ÁäÛzZ¦œ‚9ûÊXžÜ“g‰]Vϱ•0gt¡¿…žw·
\ No newline at end of file
diff --git a/hosts/nixos/porthos/secrets/matrix/secret.age b/hosts/nixos/porthos/secrets/matrix/secret.age
new file mode 100644
index 0000000..539c33e
Binary files /dev/null and b/hosts/nixos/porthos/secrets/matrix/secret.age differ
diff --git a/hosts/nixos/porthos/secrets/miniflux/credentials.age b/hosts/nixos/porthos/secrets/miniflux/credentials.age
new file mode 100644
index 0000000..9790159
Binary files /dev/null and b/hosts/nixos/porthos/secrets/miniflux/credentials.age differ
diff --git a/hosts/nixos/porthos/secrets/monitoring/password.age b/hosts/nixos/porthos/secrets/monitoring/password.age
new file mode 100644
index 0000000..410536f
--- /dev/null
+++ b/hosts/nixos/porthos/secrets/monitoring/password.age
@@ -0,0 +1,10 @@
+age-encryption.org/v1
+-> ssh-ed25519 cKojmg OdLtFHbHbc28rUn47vgsVvXxFNg9nF+9y9R6XOK390Y
+yQQYUPQGjN2+xrSqqBYa7/zS618KrVjX5Amw2MFuSLg
+-> ssh-ed25519 jPowng NwUjiLtiXVi6XFmht5l1CxEs3gm0oN4vHYwDZyda7Q4
+di6znVjNRO6QdqteVNkeot5Ko2NwWLe6v+zVR3f+o10
+-> 4Vx%\(-grease ^^Z>EC91 R 2BJ d48Wip*s
+yPiBgChRF31XgxccQFLO3MzRL7+5s29sfRoF3W1yUX6Bu59MpxD4D+n/jhLcxSH/
+CxW7KaiOctNmPm5tWh6qjmgQ+V4bcAji5vo4FKs40l56cfyueEJj+Q
+--- WUGF28zqK9E1AlOeeCtSHxFg6ikRy85gOoLtBd4m0y0
+.|…rr>©†ðìì1ÅÆ2SÉž.×hwwqºš%i˜øé ‚*U^)Öè'qžµ›O2ÓœümòQÝ7˜¯m`
\ No newline at end of file
diff --git a/hosts/nixos/porthos/secrets/monitoring/secret-key.age b/hosts/nixos/porthos/secrets/monitoring/secret-key.age
new file mode 100644
index 0000000..4cef94f
Binary files /dev/null and b/hosts/nixos/porthos/secrets/monitoring/secret-key.age differ
diff --git a/hosts/nixos/porthos/secrets/nextcloud/password.age b/hosts/nixos/porthos/secrets/nextcloud/password.age
new file mode 100644
index 0000000..9fd3c53
Binary files /dev/null and b/hosts/nixos/porthos/secrets/nextcloud/password.age differ
diff --git a/hosts/nixos/porthos/secrets/nix-serve/cache-key.age b/hosts/nixos/porthos/secrets/nix-serve/cache-key.age
new file mode 100644
index 0000000..e0fb5be
Binary files /dev/null and b/hosts/nixos/porthos/secrets/nix-serve/cache-key.age differ
diff --git a/hosts/nixos/porthos/secrets/paperless/password.age b/hosts/nixos/porthos/secrets/paperless/password.age
new file mode 100644
index 0000000..3fe76cb
--- /dev/null
+++ b/hosts/nixos/porthos/secrets/paperless/password.age
@@ -0,0 +1,10 @@
+age-encryption.org/v1
+-> ssh-ed25519 cKojmg zhpo89xef68JoeOFWzhdFshrj2BXXUCFPMLVJzv6EyE
+fmJxJi5rmyai9qGwDo7iHg4BrObGre96KCpl+g91O6I
+-> ssh-ed25519 jPowng INA6EZdy4J1p3QY5mfVOQXiLdOjIDaZR+CZMP+GfkXM
+8Nf5soaxY5SEzeJca5kaJkx7ByOvc4NkJVetB7wpEmo
+-> xjK'w-grease
+f5v0cvlt4JbHlAwDOob86qOInWdlN/oohTg
+--- NTGv4rr+MhJ/YeZhVHOjoS1V+zCHFf2itJYfK36R+wE
+š×—®JÚ dõ– oªê'YFUŸ@
+r7”ã“_N$‰ÿ–è‡>‚¡ê]hq»-¨FÛ°qXÿ?Î|?µÊ
\ No newline at end of file
diff --git a/hosts/nixos/porthos/secrets/paperless/secret-key.age b/hosts/nixos/porthos/secrets/paperless/secret-key.age
new file mode 100644
index 0000000..eae5c56
--- /dev/null
+++ b/hosts/nixos/porthos/secrets/paperless/secret-key.age
@@ -0,0 +1,10 @@
+age-encryption.org/v1
+-> ssh-ed25519 cKojmg tZwn2usN6K62oS4vBa6boh9zEp/+cS4chP8boXG6SH4
+Fr3kV8gUDoiDqMxPYWsHyww8umYhQEKhqbVBiVw5NeI
+-> ssh-ed25519 jPowng wRbJl4G85obH/GluQBBsXE7MOvooEui65eqHfurvuQs
+KqVZMBSyHhkayEdwI6ocmA4qhHY9zYJvg1CEKM1SOa0
+-> 2E"/OFW-grease o Qp3HFe^
+bGhCNicPqt7txqxUiEWXCFs1OuQLqOqHmjHSqYQv919dqYep/xBXzi/aRf3dsdvh
+TCJCTvZG31Qxvikp
+--- xKJGbdVp+Z5h0vCBleSF2zYYYd2S5i0y4szNqjRwrDY
+Tª
/N¯¨¹i7m4‚#³MhiñP¹šÒÞ›Á¥-ÏgI÷ñ±%@E†(›iÿ7·ý©ýYg¦k±´"+㸠Àª(þ]o¨¸–ý†ð@báÊÞ§+Ï[‚Y"ÿ‘ÌBóóCR[ >-Ë.4d…¤b9v
\ No newline at end of file
diff --git a/hosts/nixos/porthos/secrets/podgrab/password.age b/hosts/nixos/porthos/secrets/podgrab/password.age
new file mode 100644
index 0000000..90e2501
--- /dev/null
+++ b/hosts/nixos/porthos/secrets/podgrab/password.age
@@ -0,0 +1,9 @@
+age-encryption.org/v1
+-> ssh-ed25519 cKojmg 8rcBI7fYHuA3jO6EzJNFaAj2niIApKDt1HQEv61AKTs
+ANxkIX/CeI7t7Zqp6wmjt/D194Z+xpeiidb+qvYzoQU
+-> ssh-ed25519 jPowng oruewwTM9X/HjjcmOPcQVdp02rQBlgJPdzvlAffs3T0
+MrO0kaNhjgOkNHuz3NrIMWXNrXOHH9dT/Fk6hoQNKyY
+-> COK%H7-grease
+6yfI90QurOKlM+kgpW8KZ/iBzDYD9yhNmjG1LQ
+--- uArz8eHg8sLO0sdlkM6cELFh+FHiI5BrM0+iXJxxiDo
+¿vývû´ÊNÊbæ@Ÿ¡ÂFÛMMíYËÆíÌ&‰’/%¤¹Ñm¨®ØtÁÖ“ªd†h„|¡ðŒß©8¼Ž Ú½¨9‚®Cã¯/Å
\ No newline at end of file
diff --git a/hosts/nixos/porthos/secrets/secrets.nix b/hosts/nixos/porthos/secrets/secrets.nix
new file mode 100644
index 0000000..498aebf
--- /dev/null
+++ b/hosts/nixos/porthos/secrets/secrets.nix
@@ -0,0 +1,77 @@
+# Host-specific secrets
+let
+ keys = import ../../../../keys;
+
+ all = [
+ # Host key
+ keys.hosts.porthos
+ # Allow me to modify the secrets anywhere
+ keys.users.ambroisie
+ ];
+in
+{
+ "acme/dns-key.age".publicKeys = all;
+
+ "backup/password.age".publicKeys = all;
+ "backup/credentials.age".publicKeys = all;
+
+ "drone/gitea.age".publicKeys = all;
+ "drone/secret.age".publicKeys = all;
+ "drone/ssh/private-key.age".publicKeys = all;
+
+ "gitea/mail-password.age" = {
+ owner = "git";
+ publicKeys = all;
+ };
+
+ "lohr/secret.age".publicKeys = all;
+ "lohr/ssh-key.age".publicKeys = all;
+
+ "matrix/mail.age" = {
+ owner = "matrix-synapse";
+ publicKeys = all;
+ };
+ "matrix/secret.age" = {
+ owner = "matrix-synapse";
+ publicKeys = all;
+ };
+
+ "miniflux/credentials.age".publicKeys = all;
+
+ "monitoring/password.age" = {
+ owner = "grafana";
+ publicKeys = all;
+ };
+ "monitoring/secret-key.age" = {
+ owner = "grafana";
+ publicKeys = all;
+ };
+
+ "nextcloud/password.age" = {
+ owner = "nextcloud";
+ publicKeys = all;
+ };
+
+ "nix-serve/cache-key.age".publicKeys = all;
+
+ "paperless/password.age".publicKeys = all;
+ "paperless/secret-key.age".publicKeys = all;
+
+ "podgrab/password.age".publicKeys = all;
+
+ "sso/auth-key.age".publicKeys = all;
+ "sso/ambroisie/password-hash.age".publicKeys = all;
+ "sso/ambroisie/totp-secret.age".publicKeys = all;
+
+ "tandoor-recipes/secret-key.age".publicKeys = all;
+
+ "transmission/credentials.age".publicKeys = all;
+
+ "vikunja/mail.age".publicKeys = all;
+
+ "wireguard/private-key.age".publicKeys = all;
+
+ "woodpecker/gitea.age".publicKeys = all;
+ "woodpecker/secret.age".publicKeys = all;
+ "woodpecker/ssh/private-key.age".publicKeys = all;
+}
diff --git a/hosts/nixos/porthos/secrets/sso/ambroisie/password-hash.age b/hosts/nixos/porthos/secrets/sso/ambroisie/password-hash.age
new file mode 100644
index 0000000..10d9eaa
Binary files /dev/null and b/hosts/nixos/porthos/secrets/sso/ambroisie/password-hash.age differ
diff --git a/hosts/nixos/porthos/secrets/sso/ambroisie/totp-secret.age b/hosts/nixos/porthos/secrets/sso/ambroisie/totp-secret.age
new file mode 100644
index 0000000..c5ce19b
Binary files /dev/null and b/hosts/nixos/porthos/secrets/sso/ambroisie/totp-secret.age differ
diff --git a/hosts/nixos/porthos/secrets/sso/auth-key.age b/hosts/nixos/porthos/secrets/sso/auth-key.age
new file mode 100644
index 0000000..4e05b15
Binary files /dev/null and b/hosts/nixos/porthos/secrets/sso/auth-key.age differ
diff --git a/hosts/nixos/porthos/secrets/sso/default.nix b/hosts/nixos/porthos/secrets/sso/default.nix
new file mode 100644
index 0000000..e65a55b
--- /dev/null
+++ b/hosts/nixos/porthos/secrets/sso/default.nix
@@ -0,0 +1,21 @@
+{ lib }:
+let
+ inherit (lib) fileContents;
+ importUser = (user: {
+ # bcrypt hashed: `htpasswd -BnC 10 ""`
+ passwordHash = fileContents (./. + "/${user}/password-hash.txt");
+ # base32 encoded: `printf '' | base32 | tr -d =`
+ totpSecret = fileContents (./. + "/${user}/totp-secret.txt");
+ });
+in
+{
+ auth_key = fileContents ./auth-key.txt;
+
+ users = lib.flip lib.genAttrs importUser [
+ "ambroisie"
+ ];
+
+ groups = {
+ root = [ "ambroisie" ];
+ };
+}
diff --git a/hosts/nixos/porthos/secrets/tandoor-recipes/secret-key.age b/hosts/nixos/porthos/secrets/tandoor-recipes/secret-key.age
new file mode 100644
index 0000000..2ec147d
Binary files /dev/null and b/hosts/nixos/porthos/secrets/tandoor-recipes/secret-key.age differ
diff --git a/hosts/nixos/porthos/secrets/transmission/credentials.age b/hosts/nixos/porthos/secrets/transmission/credentials.age
new file mode 100644
index 0000000..4f407fa
--- /dev/null
+++ b/hosts/nixos/porthos/secrets/transmission/credentials.age
@@ -0,0 +1,10 @@
+age-encryption.org/v1
+-> ssh-ed25519 cKojmg mP2H3PWJN6Pv3q6C2wci3KnXjtFAIiuGy0YH0sGIy2g
+f43QqyUQfTYznszub47kgc2Mz95zVScTDkwnG3INi9U
+-> ssh-ed25519 jPowng fENbu7+FZ1mnQQHQCLm1spLHmsQGlRoJResUJtGzYkY
+hX+AqCkLCca6m/aKtGCThi7/mCCz/TZQNJNOlOmlqyA
+-> J<-grease
+n7+CPRr4oazWnE7yzpJN2ZAI4QrGsAerloP4wNeebjQDx8+IxJq1JE0g3Yi0RxzN
+chDccuSPLYk45Ov+SD/qqqFZlQ
+--- p81HYw3LFj+qz2kiZsDcevM4ZBfvN743P9Jdi7J9XkM
+‚¢ìÛ±S·7 ‘ý£÷ÜãV»»Bðßâø±³ˆ¶ïO‰lEt˜‹Á…šqýÇ—Ø©9²ã(ØP†$Wƒ0h;÷‰±àJy¯feø‚ >·_D,PºVFp\æ"AM}èg?ÿÝ/\²Ä;ùy’¬Óš(ÑSñKË
\ No newline at end of file
diff --git a/hosts/nixos/porthos/secrets/vikunja/mail.age b/hosts/nixos/porthos/secrets/vikunja/mail.age
new file mode 100644
index 0000000..4c83acd
Binary files /dev/null and b/hosts/nixos/porthos/secrets/vikunja/mail.age differ
diff --git a/hosts/nixos/porthos/secrets/wireguard/private-key.age b/hosts/nixos/porthos/secrets/wireguard/private-key.age
new file mode 100644
index 0000000..4abe1e5
--- /dev/null
+++ b/hosts/nixos/porthos/secrets/wireguard/private-key.age
@@ -0,0 +1,10 @@
+age-encryption.org/v1
+-> ssh-ed25519 cKojmg +WwRpd2MzycutQFXyLsr2+GzSgF67Z6UuvyqYZaLd3w
+sppt8HzaZP3yxnvnhzjl18Trnz8g3VyXJ6CaVBWd7jA
+-> ssh-ed25519 jPowng wanoqGB7T8bim/WZ4IAYViFQoGzaIZSgeoTr3YKpeTY
+ihDAdGa1XVW/qQz40V1v7a7iK7tu0EHMa7ayIogpcRw
+-> l-grease |PIcZ NIr >0;*
+4o8o0bevQZ6uDSx1WxxlDCURbFCM+yK1XPdrb9aztCSvG2a+ne78E42l5rBcoH7I
+m51A8uWS4nSj36N/76v6K4kelxKzWUg
+--- O6cGbTAVbDcdmPHf7UzfZiyiRtu1yfL4sBI+CkJA1qw
+ýqýÕ$ò`¿w'èS“X¸]¥á÷ø®úî…?¤6‹Ð/ÆN(Bžò N«a”.ÿ HŽ7¿í•Iú÷Àoz‡/4:sK",7J
\ No newline at end of file
diff --git a/hosts/nixos/porthos/secrets/woodpecker/gitea.age b/hosts/nixos/porthos/secrets/woodpecker/gitea.age
new file mode 100644
index 0000000..e6ede6c
Binary files /dev/null and b/hosts/nixos/porthos/secrets/woodpecker/gitea.age differ
diff --git a/hosts/nixos/porthos/secrets/woodpecker/secret.age b/hosts/nixos/porthos/secrets/woodpecker/secret.age
new file mode 100644
index 0000000..63a4862
--- /dev/null
+++ b/hosts/nixos/porthos/secrets/woodpecker/secret.age
@@ -0,0 +1,10 @@
+age-encryption.org/v1
+-> ssh-ed25519 jPowng yz0I+AazPmamF7NOnwYNrPE/ArarU01jd2mVDJUPSTY
+6Y/YQ7gb8cAZf3zT9SKOorvfUnU7kYff+gHh8fG2mY8
+-> ssh-ed25519 cKojmg 0FZU9v8eHsVeE+EoX9Y4IgfIj/8+45waPaSnSDb961I
+L6SzJoh5xqai45scoVAa6v9zslBGFYNnZY044d470uQ
+-> I[G-grease p
+AMRQY1alSzHi/PLL80kcvnM1Z9YNfoUo9u5alWXYMyzrRsg+vXjMuBvAXg3fmnzr
+wdOowTYMRV+jEG8vzkcQTsv+f7JIyo4DvOOaPyGfWMl1
+--- ih3IAFPcN1JP3FP1vcRGnPrfk91yrnIX0m/Szkbcf7Q
+ÑmW„r‚µœ_\)Ͱ]QЦxMÃs/݃Îݪäœó‚Í6óº“k±äÅY§xïMy¶ J¿¸‹GßÃ)i2_'ÖœHF€þ.âg_Îe5³#uätñØÕ 7j„ŽPñ²'TÞ¥8´•\IàW«UùäK°1Úº9½è
\ No newline at end of file
diff --git a/hosts/nixos/porthos/secrets/woodpecker/ssh/private-key.age b/hosts/nixos/porthos/secrets/woodpecker/ssh/private-key.age
new file mode 100644
index 0000000..0211701
Binary files /dev/null and b/hosts/nixos/porthos/secrets/woodpecker/ssh/private-key.age differ
diff --git a/hosts/nixos/porthos/services.nix b/hosts/nixos/porthos/services.nix
new file mode 100644
index 0000000..e4cae5e
--- /dev/null
+++ b/hosts/nixos/porthos/services.nix
@@ -0,0 +1,165 @@
+# Deployed services
+{ config, lib, ... }:
+let
+ secrets = config.age.secrets;
+in
+{
+ # List services that you want to enable:
+ my.services = {
+ # Hosts-based adblock using unbound
+ adblock = {
+ enable = true;
+ };
+ # Backblaze B2 backup
+ backup = {
+ enable = true;
+ repository = "b2:porthos-backup";
+ # Backup every 6 hours
+ timerConfig = {
+ OnActiveSec = "6h";
+ OnUnitActiveSec = "6h";
+ };
+ passwordFile = secrets."backup/password".path;
+ credentialsFile = secrets."backup/credentials".path;
+ };
+ # My blog and related hosts
+ blog.enable = true;
+ calibre-web = {
+ enable = true;
+ libraryPath = "/data/media/library";
+ };
+ drone = {
+ enable = true;
+ runners = [ "docker" "exec" ];
+ secretFile = secrets."drone/gitea".path;
+ sharedSecretFile = secrets."drone/secret".path;
+ };
+ # Auto-ban spammy bots and incorrect logins
+ fail2ban = {
+ enable = true;
+ };
+ # Flood UI for transmission
+ flood = {
+ enable = true;
+ };
+ # Gitea forge
+ gitea = {
+ enable = true;
+ mail = {
+ enable = true;
+ host = "smtp.migadu.com:465";
+ user = lib.my.mkMailAddress "gitea" "belanyi.fr";
+ passwordFile = secrets."gitea/mail-password".path;
+ };
+ };
+ # Meta-indexers
+ indexers = {
+ prowlarr.enable = true;
+ };
+ # Jellyfin media server
+ jellyfin.enable = true;
+ # Gitea mirrorig service
+ lohr = {
+ enable = true;
+ sharedSecretFile = secrets."lohr/secret".path;
+ sshKeyFile = secrets."lohr/ssh-key".path;
+ };
+ # Matrix backend and Element chat front-end
+ matrix = {
+ enable = true;
+ mailConfigFile = secrets."matrix/mail".path;
+ # Only necessary when doing the initial registration
+ secretFile = secrets."matrix/secret".path;
+ };
+ miniflux = {
+ enable = true;
+ credentialsFiles = secrets."miniflux/credentials".path;
+ };
+ # Various monitoring dashboards
+ monitoring = {
+ enable = true;
+ grafana = {
+ passwordFile = secrets."monitoring/password".path;
+ secretKeyFile = secrets."monitoring/secret-key".path;
+ };
+ };
+ # FLOSS music streaming server
+ navidrome = {
+ enable = true;
+ musicFolder = "/data/media/music";
+ };
+ # Nextcloud self-hosted cloud
+ nextcloud = {
+ enable = true;
+ passwordFile = secrets."nextcloud/password".path;
+ };
+ nix-serve = {
+ enable = true;
+ secretKeyFile = secrets."nix-serve/cache-key".path;
+ };
+ nginx = {
+ enable = true;
+ acme = {
+ credentialsFile = secrets."acme/dns-key".path;
+ };
+ sso = {
+ authKeyFile = secrets."sso/auth-key".path;
+ users = {
+ ambroisie = {
+ passwordHashFile = secrets."sso/ambroisie/password-hash".path;
+ totpSecretFile = secrets."sso/ambroisie/totp-secret".path;
+ };
+ };
+ groups = {
+ root = [ "ambroisie" ];
+ };
+ };
+ };
+ paperless = {
+ enable = true;
+ documentPath = "/data/media/paperless";
+ passwordFile = secrets."paperless/password".path;
+ secretKeyFile = secrets."paperless/secret-key".path;
+ };
+ # The whole *arr software suite
+ pirate.enable = true;
+ # Podcast automatic downloader
+ podgrab = {
+ enable = true;
+ passwordFile = secrets."podgrab/password".path;
+ port = 9598;
+ };
+ # Regular backups
+ postgresql-backup.enable = true;
+ # RSS provider for websites that do not provide any feeds
+ rss-bridge.enable = true;
+ # Usenet client
+ sabnzbd.enable = true;
+ # Because I stilll need to play sysadmin
+ ssh-server.enable = true;
+ # Recipe manager
+ tandoor-recipes = {
+ enable = true;
+ secretKeyFile = secrets."tandoor-recipes/secret-key".path;
+ };
+ # Torrent client and webui
+ transmission = {
+ enable = true;
+ credentialsFile = secrets."transmission/credentials".path;
+ };
+ # Simple, in-kernel VPN
+ wireguard = {
+ enable = true;
+ startAtBoot = true; # Server must be started to ensure clients can connect
+ };
+ woodpecker = {
+ enable = true;
+ # Avoid clashes with drone
+ port = 3035;
+ rpcPort = 3036;
+ runners = [ "docker" "exec" ];
+ secretFile = secrets."woodpecker/gitea".path;
+ sharedSecretFile = secrets."woodpecker/secret".path;
+ };
+ };
+}
diff --git a/hosts/nixos/porthos/ssh/drone.pub b/hosts/nixos/porthos/ssh/drone.pub
new file mode 100644
index 0000000..d1f971c
--- /dev/null
+++ b/hosts/nixos/porthos/ssh/drone.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDWVUvOT1/triPcj7wiLAmiVkPZ71crySbReetHaGxMYYdKNFurJQsP6BqsdCAwrGbduLUDJovLtjOM7SxghjkGqh2RZucj/zqpja8YoFqYTcLutlqa1NwUqQTq21azKBDSdvkBPWWyZhOKssnag+0bZRN3vVajoDrwAU6zJLhHh9eNESTEytAnZnllXsHB1dKF1p7FWVwYTGAc1PdHHSQNMkjg9aCM+VBzTHhp8nF+GOtGzt0A0XnoZGdhn6KqhKyH7KxwPMmeD3RNeCEmQY/TXjthOx/mBkgTEa8LWOBxdy/Rs6edUenvPcQ5tK5nX0GSxxqtbORlhT+tGiqq1UHeIUhXirBUaS7pnDo+Edc0m8ruLcwHwyQ5yVn2ts3daKxb87+PyjYiRxxeQXvbF84ef7ZOkLTEn0tnftFHLqszBfOjoV1DmMNSWPDULD3krObzNbr1I0xHE7bDRXw2t8L1cwHOLHTL9KwsTCw1d25JSxINp2wAZxlGVZLXoXVMKTjfx1xSGbUuRzA1Q6+1IH9WDvSSixDzvc2Dqnj91/xardivApK+T+OxTBurwWsxzEezIAbTCpoKW9ulzu06xWGWhxATkzUmVh/qhFUHAVlmhEvn0KqlYbWteEcUxgKS1uSAoA6+pZh5NMG1u1hLEktBQbDnS0VdyKYBUHZidLuR4w== ambroisie@porthos
diff --git a/machines/porthos/users.nix b/hosts/nixos/porthos/users.nix
similarity index 64%
rename from machines/porthos/users.nix
rename to hosts/nixos/porthos/users.nix
index 1a26e3c..fbbe368 100644
--- a/machines/porthos/users.nix
+++ b/hosts/nixos/porthos/users.nix
@@ -1,8 +1,5 @@
# User setup
-{ config, ... }:
-let
- my = config.my;
-in
+{ ... }:
{
users.users.blog = {
description = "Blog Publisher";
@@ -10,6 +7,6 @@ in
group = "nginx";
createHome = false; # Messes with permissions
home = "/var/www/";
- openssh.authorizedKeys.keys = [ my.secrets.drone.ssh.publicKey ];
+ openssh.authorizedKeys.keyFiles = [ ./ssh/drone.pub ];
};
}
diff --git a/keys/default.nix b/keys/default.nix
new file mode 100644
index 0000000..a538328
--- /dev/null
+++ b/keys/default.nix
@@ -0,0 +1,39 @@
+# Populate agenix keys from a central location
+let
+ inherit (builtins)
+ mapAttrs
+ readDir
+ readFile
+ stringLength
+ substring
+ ;
+
+ removeSuffix = suffix: str:
+ let
+ sufLen = stringLength suffix;
+ sLen = stringLength str;
+ in
+ if sufLen <= sLen && suffix == substring (sLen - sufLen) sufLen str then
+ substring 0 (sLen - sufLen) str
+ else
+ str;
+
+
+ readKeys = dir:
+ let
+ files = readDir dir;
+ readNoNewlines = f: removeSuffix "\n" (readFile f);
+ readKey = name: readNoNewlines (dir + "/${name}");
+ in
+ mapAttrs (n: _: readKey n) files;
+
+ hosts = readKeys ./hosts;
+ users = readKeys ./users;
+in
+{
+ inherit
+ hosts
+ users;
+
+ all = (builtins.attrValues hosts) ++ (builtins.attrValues users);
+}
diff --git a/keys/hosts/porthos b/keys/hosts/porthos
new file mode 100644
index 0000000..7156513
--- /dev/null
+++ b/keys/hosts/porthos
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICGzznQ3LSmBYHx6fXthgMDiTcU5i/Nvj020SbmhzAFb root@porthos
diff --git a/keys/users/ambroisie b/keys/users/ambroisie
new file mode 100644
index 0000000..cf08a3c
--- /dev/null
+++ b/keys/users/ambroisie
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMIVd6Oh08iUNb1vTULbxGpevnh++wxsWW9wqhaDryIq ambroisie@agenix
diff --git a/lib/attrs.nix b/lib/attrs.nix
index 4595467..31686ac 100644
--- a/lib/attrs.nix
+++ b/lib/attrs.nix
@@ -1,7 +1,57 @@
{ lib, ... }:
let
- inherit (lib) filterAttrs mapAttrs';
+ inherit (lib)
+ filterAttrs
+ foldl
+ listToAttrs
+ mapAttrs'
+ nameValuePair
+ recursiveUpdate
+ ;
in
{
+ # Filter a generated set of attrs using a predicate function.
+ #
+ # mapFilterAttrs ::
+ # (name -> value -> bool)
+ # (name -> value -> { name = any; value = any; })
+ # attrs
mapFilterAttrs = pred: f: attrs: filterAttrs pred (mapAttrs' f attrs);
+
+ # Generate an attribute set by mapping a function over a list of values.
+ #
+ # genAttrs' ::
+ # [ values ]
+ # (value -> { name = any; value = any; })
+ # attrs
+ genAttrs' = values: f: listToAttrs (map f values);
+
+ # Merge a list of attrs non-recursively, later values override previous ones.
+ #
+ # merge ::
+ # [ attrs ]
+ # attrs
+ merge = foldl (a: b: a // b) { };
+
+ # Merge a list of attrs recursively, later values override previous ones.
+ #
+ # recursiveMerge ::
+ # [ attrs ]
+ # attrs
+ recursiveMerge = foldl recursiveUpdate { };
+
+ # Rename each of the attributes in an attribute set using the mapping function
+ #
+ # renameAttrs ::
+ # (name -> new name)
+ # attrs
+ renameAttrs = f: mapAttrs' (name: value: nameValuePair (f name) value);
+
+ # Rename each of the attributes in an attribute set using a function which
+ # takes the attribute's name and value as inputs.
+ #
+ # renameAttrs' ::
+ # (name -> value -> new name)
+ # attrs
+ renameAttrs' = f: mapAttrs' (name: value: nameValuePair (f name value) value);
}
diff --git a/lib/default.nix b/lib/default.nix
index 9014f85..894e351 100644
--- a/lib/default.nix
+++ b/lib/default.nix
@@ -12,10 +12,7 @@ let
};
mylib = makeExtensible (self:
- with self; mapModules ./.
- (file: import file { inherit self lib pkgs inputs; })
+ mapModules ./. (file: import file { inherit self lib pkgs inputs; })
);
in
-mylib.extend (self: super:
- foldr (a: b: a // b) { } (attrValues super)
-)
+mylib.extend (_self: super: foldr (a: b: a // b) { } (attrValues super))
diff --git a/lib/ip.nix b/lib/ip.nix
index 2af3fef..fcafd72 100644
--- a/lib/ip.nix
+++ b/lib/ip.nix
@@ -100,7 +100,7 @@ rec {
# Pretty print a parsed subnet into a human readable form
prettySubnet4 = { baseIp, cidr, ... }: "${prettyIp4 baseIp}/${toString cidr}";
- # Get the nth address from an IPv4 range, without checking if it is in range
+ # Get the nth address from an IPv4 range
nthInRange4 = { from, to }: n:
let
carry = lhs: { carry, acc }:
@@ -112,8 +112,15 @@ rec {
acc = [ (mod totVal 256) ] ++ acc;
};
carried = foldr carry { carry = n; acc = [ ]; } from;
+ checkInRange =
+ if (to - from) < n
+ then
+ warn ''
+ nthInRange4: '${n}'-th address outside of range (${prettyIp4 from}, ${prettyIp4 to})
+ ''
+ else id;
in
- carried.acc;
+ checkInRange carried.acc;
# Convert an IPv4 range into a list of all its constituent addresses
rangeIp4 =
diff --git a/lib/lists.nix b/lib/lists.nix
new file mode 100644
index 0000000..190198e
--- /dev/null
+++ b/lib/lists.nix
@@ -0,0 +1,27 @@
+{ lib, ... }:
+let
+ inherit (lib) filter foldl';
+in
+{
+ # Count the number of appararitions of each value in a list.
+ #
+ # countValues ::
+ # [ any ] -> ({ any = int; })
+ countValues =
+ let
+ addToCount = acc: x:
+ let
+ v = toString x;
+ in
+ acc // { ${v} = (acc.${v} or 0) + 1; };
+ in
+ foldl' addToCount { };
+
+ # Filter a list using a predicate function after applying a map.
+ #
+ # mapFilter ::
+ # (value -> bool)
+ # (any -> value)
+ # [ any ]
+ mapFilter = pred: f: attrs: filter pred (map f attrs);
+}
diff --git a/lib/modules.nix b/lib/modules.nix
index 92e8476..5b8c4c3 100644
--- a/lib/modules.nix
+++ b/lib/modules.nix
@@ -3,21 +3,48 @@ let
inherit (builtins) readDir pathExists;
inherit (lib) hasPrefix hasSuffix nameValuePair removeSuffix;
inherit (self.attrs) mapFilterAttrs;
+
+ implOptionalRecursion = recurse:
+ let
+ recurseStep =
+ if recurse
+ then (n: path: fn: nameValuePair n (impl path fn))
+ else (_: _: _: nameValuePair "" null);
+ impl = dir: fn:
+ mapFilterAttrs
+ (n: _: n != "" && !(hasPrefix "_" n))
+ (n: v:
+ let
+ path = "${toString dir}/${n}";
+ in
+ if v == "directory"
+ then
+ if pathExists "${path}/default.nix"
+ then nameValuePair n (fn path)
+ else recurseStep n path fn
+ else if v == "regular" && n != "default.nix" && hasSuffix ".nix" n
+ then nameValuePair (removeSuffix ".nix" n) (fn path)
+ else nameValuePair "" null)
+ (readDir dir);
+ in
+ impl;
in
{
- mapModules = dir: fn:
- mapFilterAttrs
- (n: v:
- v != null &&
- !(hasPrefix "_" n))
- (n: v:
- let path = "${toString dir}/${n}"; in
- if v == "directory" && pathExists "${path}/default.nix"
- then nameValuePair n (fn path)
- else if v == "regular" &&
- n != "default.nix" &&
- hasSuffix ".nix" n
- then nameValuePair (removeSuffix ".nix" n) (fn path)
- else nameValuePair "" null)
- (readDir dir);
+ # Find all nix modules in a directory, discard any prefixed with "_",
+ # map a function to each resulting path, and generate an attribute set
+ # to associate module name to resulting value.
+ #
+ # mapModules ::
+ # path
+ # (path -> any)
+ # attrs
+ mapModules = implOptionalRecursion false;
+
+ # Recursive version of mapModules.
+ #
+ # mapModulesRec ::
+ # path
+ # (path -> any)
+ # attrs
+ mapModulesRec = implOptionalRecursion true;
}
diff --git a/lib/strings.nix b/lib/strings.nix
new file mode 100644
index 0000000..a1f74e4
--- /dev/null
+++ b/lib/strings.nix
@@ -0,0 +1,7 @@
+{ ... }:
+{
+ # Make an email address from the name and domain stems
+ #
+ # mkMailAddress :: String -> String -> String
+ mkMailAddress = name: domain: "${name}@${domain}";
+}
diff --git a/machines/porthos/default.nix b/machines/porthos/default.nix
deleted file mode 100644
index d8726f2..0000000
--- a/machines/porthos/default.nix
+++ /dev/null
@@ -1,12 +0,0 @@
-# Porthos specific settings
-{ ... }:
-
-{
- imports = [
- ./boot.nix
- ./hardware.nix
- ./networking.nix
- ./services.nix
- ./users.nix
- ];
-}
diff --git a/machines/porthos/services.nix b/machines/porthos/services.nix
deleted file mode 100644
index 9bc99e7..0000000
--- a/machines/porthos/services.nix
+++ /dev/null
@@ -1,114 +0,0 @@
-# Deployed services
-{ config, ... }:
-let
- my = config.my;
-in
-{
- # List services that you want to enable:
- my.services = {
- # Hosts-based adblock using unbound
- adblock = {
- enable = true;
- };
- # Backblaze B2 backup
- backup = {
- enable = true;
- repository = "b2:porthos-backup";
- # Backup every 6 hours
- timerConfig = {
- OnActiveSec = "6h";
- OnUnitActiveSec = "6h";
- };
- # Insecure, I don't care.
- passwordFile =
- builtins.toFile "password.txt" my.secrets.backup.password;
- credentialsFile =
- builtins.toFile "creds.env" my.secrets.backup.credentials;
- };
- # My blog and related hosts
- blog.enable = true;
- calibre-web = {
- enable = true;
- libraryPath = "/data/media/library";
- };
- drone = {
- enable = true;
- runners = [ "docker" "exec" ];
- # Insecure, I don't care.
- secretFile =
- builtins.toFile "gitea.env" my.secrets.drone.gitea;
- sharedSecretFile =
- builtins.toFile "rpc.env" my.secrets.drone.secret;
- };
- # Gitea forge
- gitea.enable = true;
- # Meta-indexers
- indexers = {
- jackett.enable = true;
- nzbhydra.enable = true;
- };
- # Jellyfin media server
- jellyfin.enable = true;
- # Gitea mirrorig service
- lohr = {
- enable = true;
- sharedSecretFile =
- let
- content = "LOHR_SECRET=${my.secrets.lohr.secret}";
- in
- builtins.toFile "lohr-secret.env" content;
- };
- # Matrix backend and Element chat front-end
- matrix = {
- enable = true;
- secret = my.secrets.matrix.secret;
- };
- miniflux = {
- enable = true;
- password = my.secrets.miniflux.password;
- };
- # Nextcloud self-hosted cloud
- nextcloud = {
- enable = true;
- password = my.secrets.nextcloud.password;
- };
- # The whole *arr software suite
- pirate.enable = true;
- # Podcast automatic downloader
- podgrab = {
- enable = true;
- passwordFile =
- let
- contents = "PASSWORD=${my.secrets.podgrab.password}";
- in
- builtins.toFile "podgrab.env" contents;
- port = 9598;
- };
- # Regular backups
- postgresql-backup.enable = true;
- # An IRC client daemon
- quassel.enable = true;
- # RSS provider for websites that do not provide any feeds
- rss-bridge.enable = true;
- # Usenet client
- sabnzbd.enable = true;
- # Because I stilll need to play sysadmin
- ssh-server.enable = true;
- # Torrent client and webui
- transmission = {
- enable = true;
- username = "Ambroisie";
- password = my.secrets.transmission.password;
- };
- # Simple, in-kernel VPN
- wireguard = {
- enable = true;
- startAtBoot = true; # Server must be started to ensure clients can connect
- };
- };
-
- programs.gnupg.agent = {
- enable = true;
- enableSSHSupport = true;
- };
-}
diff --git a/modules/default.nix b/modules/default.nix
index 082a8da..2eaa2e6 100644
--- a/modules/default.nix
+++ b/modules/default.nix
@@ -1,15 +1,28 @@
# Common modules
-{ ... }:
+{ lib, ... }:
{
imports = [
- ./documentation.nix
- ./ergodox.nix
- ./language.nix
- ./media.nix
- ./networking.nix
- ./nix.nix
- ./packages.nix
- ./users.nix
+ ./hardware
+ ./home
+ ./programs
+ ./secrets
+ ./services
+ ./system
];
+
+ options.my = with lib; {
+ user = {
+ name = mkOption {
+ type = types.str;
+ default = "ambroisie";
+ example = "alice";
+ description = "my username";
+ };
+
+ home = {
+ enable = my.mkDisableOption "home-manager configuration";
+ };
+ };
+ };
}
diff --git a/modules/documentation.nix b/modules/documentation.nix
deleted file mode 100644
index 5f67197..0000000
--- a/modules/documentation.nix
+++ /dev/null
@@ -1,37 +0,0 @@
-{ config, lib, ... }:
-let
- cfg = config.my.module.documentation;
-
- # I usually want everything enabled at once, but keep it customizable
- defaultToGlobal = description: lib.mkEnableOption description // {
- default = cfg.enable;
- };
-in
-{
- options.my.module.documentation = with lib.my; {
- enable = mkDisableOption "Documentation integration";
-
- dev.enable = defaultToGlobal "Documentation aimed at developers";
-
- info.enable = defaultToGlobal "Documentation aimed at developers";
-
- man.enable = defaultToGlobal "Documentation aimed at developers";
-
- nixos.enable = defaultToGlobal "NixOS documentation";
- };
-
- config.documentation = {
- enable = cfg.enable;
-
- dev.enable = cfg.dev.enable;
-
- info.enable = cfg.info.enable;
-
- man = {
- enable = cfg.man.enable;
- generateCaches = true;
- };
-
- nixos.enable = cfg.nixos.enable;
- };
-}
diff --git a/modules/hardware/bluetooth/default.nix b/modules/hardware/bluetooth/default.nix
new file mode 100644
index 0000000..2d840f9
--- /dev/null
+++ b/modules/hardware/bluetooth/default.nix
@@ -0,0 +1,56 @@
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.my.hardware.bluetooth;
+in
+{
+ options.my.hardware.bluetooth = with lib; {
+ enable = mkEnableOption "bluetooth configuration";
+
+ enableHeadsetIntegration = my.mkDisableOption "A2DP sink configuration";
+
+ loadExtraCodecs = my.mkDisableOption "extra audio codecs";
+ };
+
+ config = lib.mkIf cfg.enable (lib.mkMerge [
+ # Enable bluetooth devices and GUI to connect to them
+ {
+ hardware.bluetooth.enable = true;
+ services.blueman.enable = true;
+ }
+
+ # Support for additional bluetooth codecs
+ (lib.mkIf cfg.loadExtraCodecs {
+ hardware.pulseaudio = {
+ extraModules = [ pkgs.pulseaudio-modules-bt ];
+ package = pkgs.pulseaudioFull;
+ };
+
+ environment.etc = {
+ "wireplumber/bluetooth.lua.d/51-bluez-config.lua".text = ''
+ bluez_monitor.properties = {
+ -- SBC XQ provides better audio
+ ["bluez5.enable-sbc-xq"] = true,
+
+ -- mSBC provides better audio + microphone
+ ["bluez5.enable-msbc"] = true,
+
+ -- Synchronize volume with bluetooth device
+ ["bluez5.enable-hw-volume"] = true,
+
+ -- FIXME: Some devices may now support both hsp_ag and hfp_ag
+ ["bluez5.headset-roles"] = "[ hsp_hs hsp_ag hfp_hf hfp_ag ]"
+ }
+ '';
+ };
+ })
+
+ # Support for A2DP audio profile
+ (lib.mkIf cfg.enableHeadsetIntegration {
+ hardware.bluetooth.settings = {
+ General = {
+ Enable = "Source,Sink,Media,Socket";
+ };
+ };
+ })
+ ]);
+}
diff --git a/modules/hardware/default.nix b/modules/hardware/default.nix
new file mode 100644
index 0000000..2a686f7
--- /dev/null
+++ b/modules/hardware/default.nix
@@ -0,0 +1,14 @@
+# Hardware-related modules
+{ ... }:
+
+{
+ imports = [
+ ./bluetooth
+ ./ergodox
+ ./firmware
+ ./mx-ergo
+ ./networking
+ ./sound
+ ./upower
+ ];
+}
diff --git a/modules/ergodox.nix b/modules/hardware/ergodox/default.nix
similarity index 64%
rename from modules/ergodox.nix
rename to modules/hardware/ergodox/default.nix
index cbc4957..77f3ecb 100644
--- a/modules/ergodox.nix
+++ b/modules/hardware/ergodox/default.nix
@@ -1,16 +1,14 @@
# ZSA keyboard udev rules
{ config, lib, ... }:
let
- cfg = config.my.modules.ergodox;
+ cfg = config.my.hardware.ergodox;
in
{
- options.my.modules.ergodox = with lib; {
+ options.my.hardware.ergodox = with lib; {
enable = mkEnableOption "ZSA udev rules and user group configuration";
};
config = lib.mkIf cfg.enable {
hardware.keyboard.zsa.enable = true;
-
- users.extraGroups = [ "plugdev" ];
};
}
diff --git a/modules/hardware/firmware/default.nix b/modules/hardware/firmware/default.nix
new file mode 100644
index 0000000..a77135c
--- /dev/null
+++ b/modules/hardware/firmware/default.nix
@@ -0,0 +1,38 @@
+{ config, lib, ... }:
+let
+ cfg = config.my.hardware.firmware;
+in
+{
+ options.my.hardware.firmware = with lib; {
+ enable = my.mkDisableOption "firmware configuration";
+
+ cpuFlavor = mkOption {
+ type = with types; nullOr (enum [ "intel" "amd" ]);
+ default = null;
+ example = "intel";
+ description = "Which kind of CPU to activate micro-code updates";
+ };
+ };
+
+ config = lib.mkIf cfg.enable (lib.mkMerge [
+ {
+ hardware = {
+ enableRedistributableFirmware = true;
+ };
+ }
+
+ # Intel CPU
+ (lib.mkIf (cfg.cpuFlavor == "intel") {
+ hardware = {
+ cpu.intel.updateMicrocode = true;
+ };
+ })
+
+ # AMD CPU
+ (lib.mkIf (cfg.cpuFlavor == "amd") {
+ hardware = {
+ cpu.amd.updateMicrocode = true;
+ };
+ })
+ ]);
+}
diff --git a/modules/hardware/mx-ergo/default.nix b/modules/hardware/mx-ergo/default.nix
new file mode 100644
index 0000000..e4e55a1
--- /dev/null
+++ b/modules/hardware/mx-ergo/default.nix
@@ -0,0 +1,26 @@
+# Hold down the `next page` button to scroll using the ball
+{ config, lib, ... }:
+let
+ cfg = config.my.hardware.mx-ergo;
+in
+{
+ options.my.hardware.mx-ergo = with lib; {
+ enable = mkEnableOption "MX Ergo configuration";
+ };
+
+ config = lib.mkIf cfg.enable {
+ services.xserver = {
+ # This section must be *after* the one configured by `libinput`
+ # for the `ScrollMethod` configuration to not be overriden
+ inputClassSections = lib.mkAfter [
+ ''
+ Identifier "MX Ergo scroll button configuration"
+ MatchProduct "MX Ergo"
+ MatchIsPointer "on"
+ Option "ScrollMethod" "button"
+ Option "ScrollButton" "9"
+ ''
+ ];
+ };
+ };
+}
diff --git a/modules/hardware/networking/default.nix b/modules/hardware/networking/default.nix
new file mode 100644
index 0000000..f0806fe
--- /dev/null
+++ b/modules/hardware/networking/default.nix
@@ -0,0 +1,27 @@
+{ config, lib, ... }:
+let
+ cfg = config.my.hardware.networking;
+in
+{
+ options.my.hardware.networking = with lib; {
+ externalInterface = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "eth0";
+ description = ''
+ Name of the network interface that egresses to the internet. Used for
+ e.g. NATing internal networks.
+ '';
+ };
+
+ wireless = {
+ enable = mkEnableOption "wireless configuration";
+ };
+ };
+
+ config = lib.mkMerge [
+ (lib.mkIf cfg.wireless.enable {
+ networking.networkmanager.enable = true;
+ })
+ ];
+}
diff --git a/modules/hardware/sound/default.nix b/modules/hardware/sound/default.nix
new file mode 100644
index 0000000..e8ba7f7
--- /dev/null
+++ b/modules/hardware/sound/default.nix
@@ -0,0 +1,63 @@
+{ config, lib, ... }:
+let
+ cfg = config.my.hardware.sound;
+in
+{
+ options.my.hardware.sound = with lib; {
+ pipewire = {
+ enable = mkEnableOption "pipewire configuration";
+ };
+
+ pulse = {
+ enable = mkEnableOption "pulseaudio configuration";
+ };
+ };
+
+ config = (lib.mkMerge [
+ # Sanity check
+ {
+ assertions = [
+ {
+ assertion = builtins.all (lib.id) [
+ (cfg.pipewire.enable -> !cfg.pulse.enable)
+ (cfg.pulse.enable -> !cfg.pipewire.enable)
+ ];
+ message = ''
+ `config.my.hardware.sound.pipewire.enable` and
+ `config.my.hardware.sound.pulse.enable` are incompatible.
+ '';
+ }
+ ];
+ }
+
+ (lib.mkIf cfg.pipewire.enable {
+ # RealtimeKit is recommended
+ security.rtkit.enable = true;
+
+ services.pipewire = {
+ enable = true;
+
+ alsa = {
+ enable = true;
+ support32Bit = true;
+ };
+
+ pulse = {
+ enable = true;
+ };
+
+ jack = {
+ enable = true;
+ };
+ };
+ })
+
+ # Pulseaudio setup
+ (lib.mkIf cfg.pulse.enable {
+ # ALSA
+ sound.enable = true;
+
+ hardware.pulseaudio.enable = true;
+ })
+ ]);
+}
diff --git a/modules/hardware/upower/default.nix b/modules/hardware/upower/default.nix
new file mode 100644
index 0000000..95fa282
--- /dev/null
+++ b/modules/hardware/upower/default.nix
@@ -0,0 +1,44 @@
+{ config, lib, ... }:
+let
+ cfg = config.my.hardware.upower;
+in
+{
+ options.my.hardware.upower = with lib; {
+ enable = mkEnableOption "upower configuration";
+
+ levels = {
+ low = mkOption {
+ type = types.ints.unsigned;
+ default = 25;
+ example = 10;
+ description = "Low percentage";
+ };
+
+ critical = mkOption {
+ type = types.ints.unsigned;
+ default = 15;
+ example = 5;
+ description = "Critical percentage";
+ };
+
+ action = mkOption {
+ type = types.ints.unsigned;
+ default = 5;
+ example = 3;
+ description = "Percentage at which point an action must be taken";
+ };
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ services.upower = {
+ enable = true;
+
+ percentageLow = cfg.levels.low;
+
+ percentageCritical = cfg.levels.critical;
+
+ percentageAction = cfg.levels.action;
+ };
+ };
+}
diff --git a/modules/home/default.nix b/modules/home/default.nix
new file mode 100644
index 0000000..1e0e0aa
--- /dev/null
+++ b/modules/home/default.nix
@@ -0,0 +1,29 @@
+{ config, inputs, lib, ... }:
+let
+ actualPath = [ "home-manager" "users" config.my.user.name "my" "home" ];
+ aliasPath = [ "my" "home" ];
+
+ cfg = config.my.user.home;
+in
+{
+ imports = [
+ inputs.home-manager.nixosModules.home-manager # enable home-manager options
+ (lib.mkAliasOptionModule aliasPath actualPath) # simplify setting home options
+ ];
+
+ config = lib.mkIf cfg.enable {
+ home-manager = {
+ # Not a fan of out-of-directory imports, but this is a good exception
+ users.${config.my.user.name} = import ../../home;
+
+ # Nix Flakes compatibility
+ useGlobalPkgs = true;
+ useUserPackages = true;
+
+ # Forward inputs to home-manager configuration
+ extraSpecialArgs = {
+ inherit inputs;
+ };
+ };
+ };
+}
diff --git a/modules/language.nix b/modules/language.nix
deleted file mode 100644
index 48d9f30..0000000
--- a/modules/language.nix
+++ /dev/null
@@ -1,7 +0,0 @@
-# Language settings
-{ ... }:
-
-{
- # Select internationalisation properties.
- i18n.defaultLocale = "en_US.UTF-8";
-}
diff --git a/modules/media.nix b/modules/media.nix
deleted file mode 100644
index 4ad2fee..0000000
--- a/modules/media.nix
+++ /dev/null
@@ -1,16 +0,0 @@
-# Abstracting away the need for a common 'media' group
-
-{ config, lib, ... }:
-let
- mediaServices = with config.my.services; [
- calibre-web
- jellyfin
- pirate
- sabnzbd
- transmission
- ];
- needed = builtins.any (service: service.enable) mediaServices;
-in
-{
- config.users.groups.media = lib.mkIf needed { };
-}
diff --git a/modules/networking.nix b/modules/networking.nix
deleted file mode 100644
index 28ba108..0000000
--- a/modules/networking.nix
+++ /dev/null
@@ -1,13 +0,0 @@
-{ lib, ... }:
-
-{
- options.my.networking.externalInterface = with lib; mkOption {
- type = types.nullOr types.str;
- default = null;
- example = "eth0";
- description = ''
- Name of the network interface that egresses to the internet. Used for
- e.g. NATing internal networks.
- '';
- };
-}
diff --git a/modules/nix.nix b/modules/nix.nix
deleted file mode 100644
index fb601a5..0000000
--- a/modules/nix.nix
+++ /dev/null
@@ -1,11 +0,0 @@
-# Nix related settings
-{ pkgs, ... }:
-
-{
- nix = {
- package = pkgs.nixFlakes;
- extraOptions = ''
- experimental-features = nix-command flakes
- '';
- };
-}
diff --git a/modules/packages.nix b/modules/packages.nix
deleted file mode 100644
index 9eb50f6..0000000
--- a/modules/packages.nix
+++ /dev/null
@@ -1,19 +0,0 @@
-# Common packages
-{ pkgs, ... }:
-
-{
- # List packages installed in system profile. To search, run:
- # $ nix search wget
- environment.systemPackages = with pkgs; [
- git
- git-crypt
- mosh
- vim
- wget
- ];
-
- programs.vim.defaultEditor = true; # Modal editing is life
- programs.zsh.enable = true; # Use integrations
-
- nixpkgs.config.allowUnfree = true; # Because I don't care *that* much.
-}
diff --git a/modules/programs/default.nix b/modules/programs/default.nix
new file mode 100644
index 0000000..73f4e4b
--- /dev/null
+++ b/modules/programs/default.nix
@@ -0,0 +1,8 @@
+# Program-related modules
+{ ... }:
+
+{
+ imports = [
+ ./steam
+ ];
+}
diff --git a/modules/programs/steam/default.nix b/modules/programs/steam/default.nix
new file mode 100644
index 0000000..0c7f9da
--- /dev/null
+++ b/modules/programs/steam/default.nix
@@ -0,0 +1,39 @@
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.my.programs.steam;
+
+ steam = pkgs.steam;
+in
+{
+ options.my.programs.steam = with lib; {
+ enable = mkEnableOption "steam configuration";
+
+ dataDir = mkOption {
+ type = types.str;
+ default = "$XDG_DATA_HOME/steamlib";
+ example = "/mnt/steam/";
+ description = ''
+ Which directory should be used as HOME to run steam.
+ '';
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ programs.steam = {
+ enable = true;
+ };
+
+ environment.systemPackages = builtins.map lib.hiPrio [
+ # Respect XDG conventions, leave my HOME alone
+ (pkgs.writeShellScriptBin "steam" ''
+ mkdir -p "${cfg.dataDir}"
+ HOME="${cfg.dataDir}" exec ${lib.getExe steam} "$@"
+ '')
+ # Same, for GOG and other such games
+ (pkgs.writeShellScriptBin "steam-run" ''
+ mkdir -p "${cfg.dataDir}"
+ HOME="${cfg.dataDir}" exec ${lib.getExe steam.run} "$@"
+ '')
+ ];
+ };
+}
diff --git a/modules/secrets/default.nix b/modules/secrets/default.nix
new file mode 100644
index 0000000..c7d6f65
--- /dev/null
+++ b/modules/secrets/default.nix
@@ -0,0 +1,24 @@
+{ config, inputs, lib, ... }:
+
+{
+ imports = [
+ inputs.agenix.nixosModules.age
+ ];
+
+ config.age = {
+ secrets =
+ let
+ toName = lib.removeSuffix ".age";
+ userExists = u: builtins.hasAttr u config.users.users;
+ # Only set the user if it exists, to avoid warnings
+ userIfExists = u: if userExists u then u else "root";
+ toSecret = name: { owner ? "root", ... }: {
+ file = ./. + "/${name}";
+ owner = lib.mkDefault (userIfExists owner);
+ };
+ convertSecrets = n: v: lib.nameValuePair (toName n) (toSecret n v);
+ secrets = import ./secrets.nix;
+ in
+ lib.mapAttrs' convertSecrets secrets;
+ };
+}
diff --git a/modules/secrets/secrets.nix b/modules/secrets/secrets.nix
new file mode 100644
index 0000000..0e685d9
--- /dev/null
+++ b/modules/secrets/secrets.nix
@@ -0,0 +1,10 @@
+# Common secrets
+let
+ keys = import ../../keys;
+
+ inherit (keys) all;
+in
+{
+ "users/ambroisie/hashed-password.age".publicKeys = all;
+ "users/root/hashed-password.age".publicKeys = all;
+}
diff --git a/modules/secrets/users/ambroisie/hashed-password.age b/modules/secrets/users/ambroisie/hashed-password.age
new file mode 100644
index 0000000..09a80f4
--- /dev/null
+++ b/modules/secrets/users/ambroisie/hashed-password.age
@@ -0,0 +1,9 @@
+age-encryption.org/v1
+-> ssh-ed25519 cKojmg vOaL2ZKsFEjX9mzQvw8Je7x2Dq8cMhrZEyBTXpH4QnE
+HXO4fbWdJsbsRmGq0IYzq8/szObxzpsGfQNNTJ4vNzg
+-> ssh-ed25519 jPowng WPxg0pP6O3ZS4dPc1WcDvzig22Fylk3mR/W9STaWbW4
+GuhFwt7M5Lc38q2LC/0eul0yP60UxmWwi9I8ToHv7bE
+-> :;V8\-grease ZC#7~eR# P<'e?vI3 9R
+lZlb44QiAaIxd0SYiRNT/QRnxxUt7npbksg
+--- 9xv4lt8IcGR8jP0UcKYYnTuh1Ix/pqXgDmevkTH9j1A
+Ï]ºcÓ3óxíwÿ'ã `ùhçÒ=X¨í·¢Ç‘g3ÆÆÄ]~ËôÞqÙ.XnÄa*€±W:–¸±,â©z®vyzñI¦æ }ÂDO=`êw“ñõ¹ˆ7:™ù“ÐRx•5$¨Ö6:ö¨´"õ,HM„"_ëÞòMÛMƒœˆBJe‰ùFá
\ No newline at end of file
diff --git a/modules/secrets/users/root/hashed-password.age b/modules/secrets/users/root/hashed-password.age
new file mode 100644
index 0000000..14986f1
Binary files /dev/null and b/modules/secrets/users/root/hashed-password.age differ
diff --git a/services/adblock.nix b/modules/services/adblock/default.nix
similarity index 63%
rename from services/adblock.nix
rename to modules/services/adblock/default.nix
index 23d63c8..45e4d6e 100644
--- a/services/adblock.nix
+++ b/modules/services/adblock/default.nix
@@ -45,21 +45,28 @@ in
services.unbound = {
enable = true;
- allowedAccess = [
- "127.0.0.0/24"
- "${wgCfg.net.v4.subnet}.0/${toString wgCfg.net.v4.mask}"
- "${wgCfg.net.v6.subnet}::0/${toString wgCfg.net.v6.mask}"
- ];
+ settings = {
+ server = {
+ access-control = [
+ "127.0.0.0/24 allow"
+ "${wgCfg.net.v4.subnet}.0/${toString wgCfg.net.v4.mask} allow"
+ "${wgCfg.net.v6.subnet}::0/${toString wgCfg.net.v6.mask} allow"
+ ];
- inherit (cfg) forwardAddresses interfaces;
+ interface = cfg.interfaces;
- extraConfig = ''
- so-reuseport: yes
- tls-cert-bundle: /etc/ssl/certs/ca-certificates.crt
- tls-upstream: yes
+ so-reuseport = true;
+ tls-cert-bundle = "/etc/ssl/certs/ca-certificates.crt";
+ tls-upstream = true;
- include: "${pkgs.ambroisie.unbound-zones-adblock}/hosts"
- '';
+ include = "${pkgs.ambroisie.unbound-zones-adblock}/hosts";
+ };
+
+ forward-zone = [{
+ name = ".";
+ forward-addr = cfg.forwardAddresses;
+ }];
+ };
};
};
}
diff --git a/services/backup.nix b/modules/services/backup/default.nix
similarity index 92%
rename from services/backup.nix
rename to modules/services/backup/default.nix
index da45b5e..ff0fc7f 100644
--- a/services/backup.nix
+++ b/modules/services/backup/default.nix
@@ -92,13 +92,13 @@ in
services.restic.backups.backblaze = {
# Take care of included and excluded files
paths = cfg.paths;
- extraOptions = with builtins; with lib;[
- (optionalString ((length cfg.exclude) != 0) excludeArg)
- ];
+ extraBackupArgs = [ "--verbose=2" ]
+ ++ lib.optional (builtins.length cfg.exclude != 0) excludeArg
+ ;
# Take care of creating the repository if it doesn't exist
initialize = true;
- # Hijack S3-related env to give B2 API key
- s3CredentialsFile = cfg.credentialsFile;
+ # give B2 API key securely
+ environmentFile = cfg.credentialsFile;
inherit (cfg) passwordFile pruneOpts timerConfig repository;
};
diff --git a/modules/services/blog/default.nix b/modules/services/blog/default.nix
new file mode 100644
index 0000000..4b646c3
--- /dev/null
+++ b/modules/services/blog/default.nix
@@ -0,0 +1,46 @@
+# My blog setup
+{ config, lib, ... }:
+let
+ cfg = config.my.services.blog;
+ domain = config.networking.domain;
+
+ makeHostInfo = subdomain: {
+ inherit subdomain;
+ root = "/var/www/${subdomain}";
+ };
+
+ hostsInfo = map makeHostInfo [ "cv" "dev" "key" ];
+in
+{
+ options.my.services.blog = {
+ enable = lib.mkEnableOption "Blog hosting";
+ };
+
+ config = lib.mkIf cfg.enable {
+ services.nginx.virtualHosts = {
+ # This is not a subdomain, cannot use my nginx wrapper module
+ ${domain} = {
+ forceSSL = true;
+ useACMEHost = domain;
+ root = "/var/www/blog";
+
+ # http://www.gnuterrypratchett.com/
+ extraConfig = ''
+ add_header X-Clacks-Overhead "GNU Terry Pratchett";
+ '';
+ };
+
+ # Dummy vhost to redirect all unknown (sub-)domains to my blog
+ "_" = {
+ forceSSL = true;
+ useACMEHost = domain;
+ default = true;
+
+ locations."/".return = "302 https://belanyi.fr$request_uri";
+ };
+ };
+
+ # Those are all subdomains, no problem
+ my.services.nginx.virtualHosts = hostsInfo;
+ };
+}
diff --git a/services/calibre-web.nix b/modules/services/calibre-web/default.nix
similarity index 57%
rename from services/calibre-web.nix
rename to modules/services/calibre-web/default.nix
index d4d7ece..858851c 100644
--- a/services/calibre-web.nix
+++ b/modules/services/calibre-web/default.nix
@@ -1,8 +1,6 @@
{ config, lib, ... }:
let
cfg = config.my.services.calibre-web;
- domain = config.networking.domain;
- calibreDomain = "library.${domain}";
in
{
options.my.services.calibre-web = with lib; {
@@ -39,18 +37,37 @@ in
};
};
- services.nginx.virtualHosts."${calibreDomain}" = {
- forceSSL = true;
- useACMEHost = domain;
+ # Set-up media group
+ users.groups.media = { };
- locations."/".proxyPass = "http://127.0.0.1:${toString cfg.port}/";
- };
+ my.services.nginx.virtualHosts = [
+ {
+ subdomain = "library";
+ inherit (cfg) port;
+ }
+ ];
my.services.backup = {
paths = [
- "/var/lib/calibre-web" # For `app.db` and `gdrive.db`
+ "/var/lib/${config.services.calibre-web.dataDir}" # For `app.db` and `gdrive.db`
cfg.libraryPath
];
};
+
+ services.fail2ban.jails = {
+ calibre-web = ''
+ enabled = true
+ filter = calibre-web
+ port = http,https
+ '';
+ };
+
+ environment.etc = {
+ "fail2ban/filter.d/calibre-web.conf".text = ''
+ [Definition]
+ failregex = ^.*Login failed for user ".*" IP-address: $
+ journalmatch = _SYSTEMD_UNIT=calibre-web.service
+ '';
+ };
};
}
diff --git a/modules/services/default.nix b/modules/services/default.nix
new file mode 100644
index 0000000..86badf5
--- /dev/null
+++ b/modules/services/default.nix
@@ -0,0 +1,40 @@
+{ ... }:
+
+{
+ imports = [
+ ./adblock
+ ./backup
+ ./blog
+ ./calibre-web
+ ./drone
+ ./fail2ban
+ ./flood
+ ./gitea
+ ./grocy
+ ./indexers
+ ./jellyfin
+ ./lohr
+ ./matrix
+ ./miniflux
+ ./monitoring
+ ./navidrome
+ ./nextcloud
+ ./nginx
+ ./nix-serve
+ ./paperless
+ ./pirate
+ ./podgrab
+ ./postgresql
+ ./postgresql-backup
+ ./quassel
+ ./rss-bridge
+ ./sabnzbd
+ ./ssh-server
+ ./tandoor-recipes
+ ./tlp
+ ./transmission
+ ./vikunja
+ ./wireguard
+ ./woodpecker
+ ];
+}
diff --git a/modules/services/drone/default.nix b/modules/services/drone/default.nix
new file mode 100644
index 0000000..79c48dd
--- /dev/null
+++ b/modules/services/drone/default.nix
@@ -0,0 +1,44 @@
+# A docker-based CI/CD system
+#
+# Inspired by [1]
+# [1]: https://github.com/Mic92/dotfiles/blob/master/nixos/eve/modules/drone.nix
+{ lib, ... }:
+{
+ imports = [
+ ./runner-docker
+ ./runner-exec
+ ./server
+ ];
+
+ options.my.services.drone = with lib; {
+ enable = mkEnableOption "Drone CI";
+ runners = mkOption {
+ type = with types; listOf (enum [ "exec" "docker" ]);
+ default = [ ];
+ example = [ "exec" "docker" ];
+ description = "Types of runners to enable";
+ };
+ admin = mkOption {
+ type = types.str;
+ default = "ambroisie";
+ example = "admin";
+ description = "Name of the admin user";
+ };
+ port = mkOption {
+ type = types.port;
+ default = 3030;
+ example = 8080;
+ description = "Internal port of the Drone UI";
+ };
+ secretFile = mkOption {
+ type = types.str;
+ example = "/run/secrets/drone-gitea.env";
+ description = "Secrets to inject into Drone server";
+ };
+ sharedSecretFile = mkOption {
+ type = types.str;
+ example = "/run/secrets/drone-rpc.env";
+ description = "Shared RPC secret to inject into server and runners";
+ };
+ };
+}
diff --git a/modules/services/drone/runner-docker/default.nix b/modules/services/drone/runner-docker/default.nix
new file mode 100644
index 0000000..e53c608
--- /dev/null
+++ b/modules/services/drone/runner-docker/default.nix
@@ -0,0 +1,43 @@
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.my.services.drone;
+ hasRunner = (name: builtins.elem name cfg.runners);
+in
+{
+ config = lib.mkIf (cfg.enable && hasRunner "docker") {
+ systemd.services.drone-runner-docker = {
+ wantedBy = [ "multi-user.target" ];
+ after = [ "docker.socket" ]; # Needs the socket to be available
+ # might break deployment
+ restartIfChanged = false;
+ confinement.enable = true;
+ serviceConfig = {
+ Environment = [
+ "DRONE_SERVER_HOST=drone.${config.networking.domain}"
+ "DRONE_SERVER_PROTO=https"
+ "DRONE_RUNNER_CAPACITY=10"
+ "CLIENT_DRONE_RPC_HOST=127.0.0.1:${toString cfg.port}"
+ ];
+ BindPaths = [
+ "/var/run/docker.sock"
+ ];
+ EnvironmentFile = [
+ cfg.sharedSecretFile
+ ];
+ ExecStart = lib.getExe pkgs.drone-runner-docker;
+ User = "drone-runner-docker";
+ Group = "drone-runner-docker";
+ };
+ };
+
+ # Make sure it is activated in that case
+ my.system.docker.enable = true;
+
+ users.users.drone-runner-docker = {
+ isSystemUser = true;
+ group = "drone-runner-docker";
+ extraGroups = [ "docker" ]; # Give access to the daemon
+ };
+ users.groups.drone-runner-docker = { };
+ };
+}
diff --git a/modules/services/drone/runner-exec/default.nix b/modules/services/drone/runner-exec/default.nix
new file mode 100644
index 0000000..a9bb563
--- /dev/null
+++ b/modules/services/drone/runner-exec/default.nix
@@ -0,0 +1,67 @@
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.my.services.drone;
+ hasRunner = (name: builtins.elem name cfg.runners);
+in
+{
+ config = lib.mkIf (cfg.enable && hasRunner "exec") {
+ systemd.services.drone-runner-exec = {
+ wantedBy = [ "multi-user.target" ];
+ # might break deployment
+ restartIfChanged = false;
+ confinement.enable = true;
+ confinement.packages = with pkgs; [
+ git
+ gnutar
+ bash
+ nix
+ gzip
+ ];
+ path = with pkgs; [
+ git
+ gnutar
+ bash
+ nix
+ gzip
+ ];
+ serviceConfig = {
+ Environment = [
+ "DRONE_SERVER_HOST=drone.${config.networking.domain}"
+ "DRONE_SERVER_PROTO=https"
+ "DRONE_RUNNER_CAPACITY=10"
+ "CLIENT_DRONE_RPC_HOST=127.0.0.1:${toString cfg.port}"
+ "NIX_REMOTE=daemon"
+ "PAGER=cat"
+ ];
+ BindPaths = [
+ "/nix/var/nix/daemon-socket/socket"
+ "/run/nscd/socket"
+ ];
+ BindReadOnlyPaths = [
+ "/etc/resolv.conf:/etc/resolv.conf"
+ "/etc/resolvconf.conf:/etc/resolvconf.conf"
+ "/etc/passwd:/etc/passwd"
+ "/etc/group:/etc/group"
+ "/nix/var/nix/profiles/system/etc/nix:/etc/nix"
+ "${config.environment.etc."ssl/certs/ca-certificates.crt".source}:/etc/ssl/certs/ca-certificates.crt"
+ "${config.environment.etc."ssh/ssh_known_hosts".source}:/etc/ssh/ssh_known_hosts"
+ "/etc/machine-id"
+ # channels are dynamic paths in the nix store, therefore we need to bind mount the whole thing
+ "/nix/"
+ ];
+ EnvironmentFile = [
+ cfg.sharedSecretFile
+ ];
+ ExecStart = lib.getExe pkgs.drone-runner-exec;
+ User = "drone-runner-exec";
+ Group = "drone-runner-exec";
+ };
+ };
+
+ users.users.drone-runner-exec = {
+ isSystemUser = true;
+ group = "drone-runner-exec";
+ };
+ users.groups.drone-runner-exec = { };
+ };
+}
diff --git a/modules/services/drone/server/default.nix b/modules/services/drone/server/default.nix
new file mode 100644
index 0000000..1202010
--- /dev/null
+++ b/modules/services/drone/server/default.nix
@@ -0,0 +1,56 @@
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.my.services.drone;
+in
+{
+ config = lib.mkIf cfg.enable {
+ systemd.services.drone-server = {
+ wantedBy = [ "multi-user.target" ];
+ after = [ "postgresql.service" ];
+ serviceConfig = {
+ EnvironmentFile = [
+ cfg.secretFile
+ cfg.sharedSecretFile
+ ];
+ Environment = [
+ "DRONE_DATABASE_DATASOURCE=postgres:///drone?host=/run/postgresql"
+ "DRONE_SERVER_HOST=drone.${config.networking.domain}"
+ "DRONE_SERVER_PROTO=https"
+ "DRONE_DATABASE_DRIVER=postgres"
+ "DRONE_SERVER_PORT=:${toString cfg.port}"
+ "DRONE_USER_CREATE=username:${cfg.admin},admin:true"
+ "DRONE_JSONNET_ENABLED=true"
+ "DRONE_STARLARK_ENABLED=true"
+ ];
+ ExecStart = "${pkgs.drone}/bin/drone-server";
+ User = "drone";
+ Group = "drone";
+ };
+ };
+
+ users.users.drone = {
+ isSystemUser = true;
+ createHome = true;
+ group = "drone";
+ };
+ users.groups.drone = { };
+
+ services.postgresql = {
+ enable = true;
+ ensureDatabases = [ "drone" ];
+ ensureUsers = [{
+ name = "drone";
+ ensurePermissions = {
+ "DATABASE drone" = "ALL PRIVILEGES";
+ };
+ }];
+ };
+
+ my.services.nginx.virtualHosts = [
+ {
+ subdomain = "drone";
+ inherit (cfg) port;
+ }
+ ];
+ };
+}
diff --git a/modules/services/fail2ban/default.nix b/modules/services/fail2ban/default.nix
new file mode 100644
index 0000000..d62b9e2
--- /dev/null
+++ b/modules/services/fail2ban/default.nix
@@ -0,0 +1,37 @@
+# A minimalist, opinionated feed reader
+{ config, lib, ... }:
+let
+ cfg = config.my.services.fail2ban;
+ wgNetCfg = config.my.services.wireguard.net;
+in
+{
+ options.my.services.fail2ban = with lib; {
+ enable = mkEnableOption "fail2ban daemon";
+ };
+
+ config = lib.mkIf cfg.enable {
+ services.fail2ban = {
+ enable = true;
+
+ ignoreIP = [
+ # Wireguard IPs
+ "${wgNetCfg.v4.subnet}.0/${toString wgNetCfg.v4.mask}"
+ "${wgNetCfg.v6.subnet}::/${toString wgNetCfg.v6.mask}"
+ # Loopback addresses
+ "127.0.0.0/8"
+ ];
+
+ maxretry = 5;
+
+ bantime-increment = {
+ enable = true;
+ rndtime = "5m"; # Use 5 minute jitter to avoid unban evasion
+ };
+
+ jails.DEFAULT.settings = {
+ findtime = "4h";
+ bantime = "10m";
+ };
+ };
+ };
+}
diff --git a/modules/services/flood/default.nix b/modules/services/flood/default.nix
new file mode 100644
index 0000000..ff5d941
--- /dev/null
+++ b/modules/services/flood/default.nix
@@ -0,0 +1,50 @@
+# A nice UI for various torrent clients
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.my.services.flood;
+in
+{
+ options.my.services.flood = with lib; {
+ enable = mkEnableOption "Flood UI";
+
+ port = mkOption {
+ type = types.port;
+ default = 9092;
+ example = 3000;
+ description = "Internal port for Flood UI";
+ };
+
+ stateDir = mkOption {
+ type = types.str;
+ default = "flood";
+ example = "floodUI";
+ description = "Directory under `/var/run` for storing Flood's files";
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ systemd.services.flood = {
+ description = "Flood torrent UI";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ ExecStart = lib.concatStringsSep " " [
+ (lib.getExe pkgs.flood)
+ "--port ${builtins.toString cfg.port}"
+ "--rundir /var/lib/${cfg.stateDir}"
+ ];
+ DynamicUser = true;
+ StateDirectory = cfg.stateDir;
+ ReadWritePaths = "";
+ };
+ };
+
+ my.services.nginx.virtualHosts = [
+ {
+ subdomain = "flood";
+ inherit (cfg) port;
+ }
+ ];
+ };
+}
diff --git a/modules/services/gitea/default.nix b/modules/services/gitea/default.nix
new file mode 100644
index 0000000..28a448d
--- /dev/null
+++ b/modules/services/gitea/default.nix
@@ -0,0 +1,154 @@
+# A low-ressource, full-featured git forge.
+{ config, lib, ... }:
+let
+ cfg = config.my.services.gitea;
+in
+{
+ options.my.services.gitea = with lib; {
+ enable = mkEnableOption "Gitea";
+ port = mkOption {
+ type = types.port;
+ default = 3042;
+ example = 8080;
+ description = "Internal port";
+ };
+ mail = {
+ enable = mkEnableOption {
+ description = "mailer configuration";
+ };
+ host = mkOption {
+ type = types.str;
+ example = "smtp.example.com:465";
+ description = "Host for the mail account";
+ };
+ user = mkOption {
+ type = types.str;
+ example = "gitea@example.com";
+ description = "User for the mail account";
+ };
+ passwordFile = mkOption {
+ type = types.str;
+ example = "/run/secrets/gitea-mail-password.txt";
+ description = "Password for the mail account";
+ };
+ type = mkOption {
+ type = types.str;
+ default = "smtp";
+ example = "smtp";
+ description = "Password for the mail account";
+ };
+ tls = mkOption {
+ type = types.bool;
+ default = true;
+ example = false;
+ description = "Use TLS for connection";
+ };
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ services.gitea =
+ let
+ inherit (config.networking) domain;
+ giteaDomain = "git.${domain}";
+ in
+ {
+ enable = true;
+
+ appName = "Ambroisie's forge";
+
+ user = "git";
+ lfs.enable = true;
+
+ useWizard = false;
+
+ database = {
+ type = "postgres"; # Automatic setup
+ user = "git"; # User needs to be the same as gitea user
+ };
+
+ # NixOS module uses `gitea dump` to backup repositories and the database,
+ # but it produces a single .zip file that's not very backup friendly.
+ # I configure my backup system manually below.
+ dump.enable = false;
+
+ mailerPasswordFile = lib.mkIf cfg.mail.enable cfg.mail.passwordFile;
+
+ settings = {
+ server = {
+ HTTP_PORT = cfg.port;
+ DOMAIN = giteaDomain;
+ ROOT_URL = "https://${giteaDomain}";
+ };
+
+ mailer = lib.mkIf cfg.mail.enable {
+ ENABLED = true;
+ HOST = cfg.mail.host;
+ FROM = cfg.mail.user;
+ USER = cfg.mail.user;
+ MAILER_TYPE = cfg.mail.type;
+ IS_TLS_ENABLED = cfg.mail.tls;
+ };
+
+ service = {
+ DISABLE_REGISTRATION = true;
+ };
+
+ session = {
+ # only send cookies via HTTPS
+ COOKIE_SECURE = true;
+ };
+ };
+ };
+
+ users.users.git = {
+ description = "Gitea Service";
+ home = config.services.gitea.stateDir;
+ useDefaultShell = true;
+ group = "git";
+
+ # The service for gitea seems to hardcode the group as
+ # gitea, so, uh, just in case?
+ extraGroups = [ "gitea" ];
+
+ isSystemUser = true;
+ };
+ users.groups.git = { };
+
+ my.services.nginx.virtualHosts = [
+ # Proxy to Gitea
+ {
+ subdomain = "git";
+ inherit (cfg) port;
+ }
+ # Redirect `gitea.` to actual forge subdomain
+ {
+ subdomain = "gitea";
+ redirect = config.services.gitea.settings.server.ROOT_URL;
+ }
+ ];
+
+ my.services.backup = {
+ paths = [
+ config.services.gitea.lfs.contentDir
+ config.services.gitea.repositoryRoot
+ ];
+ };
+
+ services.fail2ban.jails = {
+ gitea = ''
+ enabled = true
+ filter = gitea
+ action = iptables-allports
+ '';
+ };
+
+ environment.etc = {
+ "fail2ban/filter.d/gitea.conf".text = ''
+ [Definition]
+ failregex = ^.*(Failed authentication attempt|invalid credentials|Attempted access of unknown user).* from $
+ journalmatch = _SYSTEMD_UNIT=gitea.service
+ '';
+ };
+ };
+}
diff --git a/modules/services/grocy/default.nix b/modules/services/grocy/default.nix
new file mode 100644
index 0000000..87927d6
--- /dev/null
+++ b/modules/services/grocy/default.nix
@@ -0,0 +1,40 @@
+# Groceries and household management
+{ config, lib, ... }:
+let
+ cfg = config.my.services.grocy;
+ grocyDomain = "grocy.${config.networking.domain}";
+in
+{
+ options.my.services.grocy = with lib; {
+ enable = mkEnableOption "Grocy household ERP";
+ };
+
+ config = lib.mkIf cfg.enable {
+ services.grocy = {
+ enable = true;
+
+ # The service sets up the reverse proxy automatically
+ hostName = grocyDomain;
+
+ # Configure SSL by hand
+ nginx = {
+ enableSSL = false;
+ };
+
+ settings = {
+ currency = "EUR";
+ culture = "en";
+ calendar = {
+ # Start on Monday
+ firstDayOfWeek = 1;
+ showWeekNumber = true;
+ };
+ };
+ };
+
+ services.nginx.virtualHosts."${grocyDomain}" = {
+ forceSSL = true;
+ useACMEHost = config.networking.domain;
+ };
+ };
+}
diff --git a/modules/services/indexers/default.nix b/modules/services/indexers/default.nix
new file mode 100644
index 0000000..66f1604
--- /dev/null
+++ b/modules/services/indexers/default.nix
@@ -0,0 +1,65 @@
+# Torrent and usenet meta-indexers
+{ config, lib, ... }:
+let
+ cfg = config.my.services.indexers;
+
+ jackettPort = 9117;
+ nzbhydraPort = 5076;
+ prowlarrPort = 9696;
+in
+{
+ options.my.services.indexers = with lib; {
+ jackett.enable = mkEnableOption "Jackett torrent meta-indexer";
+ nzbhydra.enable = mkEnableOption "NZBHydra2 usenet meta-indexer";
+ prowlarr.enable = mkEnableOption "Prowlarr torrent & usenet meta-indexer";
+ };
+
+ config = lib.mkMerge [
+ (lib.mkIf cfg.jackett.enable {
+ services.jackett = {
+ enable = true;
+ };
+
+ # Jackett wants to eat *all* my RAM if left to its own devices
+ systemd.services.jackett = {
+ serviceConfig = {
+ MemoryHigh = "15%";
+ MemoryMax = "25%";
+ };
+ };
+
+ my.services.nginx.virtualHosts = [
+ {
+ subdomain = "jackett";
+ port = jackettPort;
+ }
+ ];
+ })
+
+ (lib.mkIf cfg.nzbhydra.enable {
+ services.nzbhydra2 = {
+ enable = true;
+ };
+
+ my.services.nginx.virtualHosts = [
+ {
+ subdomain = "nzbhydra";
+ port = nzbhydraPort;
+ }
+ ];
+ })
+
+ (lib.mkIf cfg.prowlarr.enable {
+ services.prowlarr = {
+ enable = true;
+ };
+
+ my.services.nginx.virtualHosts = [
+ {
+ subdomain = "prowlarr";
+ port = prowlarrPort;
+ }
+ ];
+ })
+ ];
+}
diff --git a/modules/services/jellyfin/default.nix b/modules/services/jellyfin/default.nix
new file mode 100644
index 0000000..2fcf51e
--- /dev/null
+++ b/modules/services/jellyfin/default.nix
@@ -0,0 +1,39 @@
+# A FLOSS media server
+{ config, lib, ... }:
+let
+ cfg = config.my.services.jellyfin;
+in
+{
+ options.my.services.jellyfin = {
+ enable = lib.mkEnableOption "Jellyfin Media Server";
+ };
+
+ config = lib.mkIf cfg.enable {
+ services.jellyfin = {
+ enable = true;
+ group = "media";
+ };
+
+ # Set-up media group
+ users.groups.media = { };
+
+ my.services.nginx.virtualHosts = [
+ {
+ subdomain = "jellyfin";
+ port = 8096;
+ extraConfig = {
+ locations."/" = {
+ extraConfig = ''
+ proxy_buffering off;
+ '';
+ };
+ # Too bad for the repetition...
+ locations."/socket" = {
+ proxyPass = "http://127.0.0.1:8096/";
+ proxyWebsockets = true;
+ };
+ };
+ }
+ ];
+ };
+}
diff --git a/services/lohr.nix b/modules/services/lohr/default.nix
similarity index 58%
rename from services/lohr.nix
rename to modules/services/lohr/default.nix
index 57f4feb..245567c 100644
--- a/services/lohr.nix
+++ b/modules/services/lohr/default.nix
@@ -4,10 +4,8 @@ let
cfg = config.my.services.lohr;
settingsFormat = pkgs.formats.yaml { };
- domain = config.networking.domain;
- lohrDomain = "lohr.${config.networking.domain}";
-
- lohrPkg = pkgs.ambroisie.lohr;
+ lohrStateDirectory = "lohr";
+ lohrHome = "/var/lib/lohr/";
in
{
options.my.services.lohr = with lib; {
@@ -37,6 +35,15 @@ in
example = "/run/secrets/lohr.env";
description = "Shared secret between lohr and Gitea hook";
};
+
+ sshKeyFile = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ example = "/run/secrets/lohr/ssh-key";
+ description = ''
+ The ssh key that should be used by lohr to mirror repositories
+ '';
+ };
};
config = lib.mkIf cfg.enable {
@@ -49,39 +56,53 @@ in
Environment = [
"ROCKET_PORT=${toString cfg.port}"
"ROCKET_LOG_LEVEL=normal"
- "LOHR_HOME=/var/lib/lohr/"
+ "LOHR_HOME=${lohrHome}"
"LOHR_CONFIG="
];
+ ExecStartPre = lib.mkIf (cfg.sshKeyFile != null) ''+${
+ pkgs.writeScript "copy-ssh-key" ''
+ #!${pkgs.bash}/bin/bash
+ # Ensure the key is not there
+ mkdir -p '${lohrHome}/.ssh'
+ rm -f '${lohrHome}/.ssh/id_ed25519'
+
+ # Move the key into place
+ cp ${cfg.sshKeyFile} '${lohrHome}/.ssh/id_ed25519'
+
+ # Fix permissions
+ chown -R lohr:lohr '${lohrHome}/.ssh'
+ chmod -R 0700 '${lohrHome}/.ssh'
+ ''
+ }'';
ExecStart =
let
configFile = settingsFormat.generate "lohr-config.yaml" cfg.setting;
in
- "${lohrPkg}/bin/lohr --config ${configFile}";
- StateDirectory = "lohr";
- WorkingDirectory = "/var/lib/lohr";
+ "${lib.getExe pkgs.ambroisie.lohr} --config ${configFile}";
+ StateDirectory = lohrStateDirectory;
+ WorkingDirectory = lohrHome;
User = "lohr";
Group = "lohr";
};
path = with pkgs; [
git
+ openssh
];
};
users.users.lohr = {
isSystemUser = true;
- home = "/var/lib/lohr";
+ home = lohrHome;
createHome = true;
group = "lohr";
};
users.groups.lohr = { };
- services.nginx.virtualHosts."${lohrDomain}" = {
- forceSSL = true;
- useACMEHost = domain;
-
- locations."/" = {
- proxyPass = "http://127.0.0.1:${toString cfg.port}/";
- };
- };
+ my.services.nginx.virtualHosts = [
+ {
+ subdomain = "lohr";
+ inherit (cfg) port;
+ }
+ ];
};
}
diff --git a/services/matrix.nix b/modules/services/matrix/default.nix
similarity index 73%
rename from services/matrix.nix
rename to modules/services/matrix/default.nix
index 8dc6cad..c73afed 100644
--- a/services/matrix.nix
+++ b/modules/services/matrix/default.nix
@@ -7,7 +7,6 @@
# [1]: https://github.com/alarsyo/nixos-config/blob/main/services/matrix.nix
{ config, lib, pkgs, ... }:
-with lib;
let
cfg = config.my.services.matrix;
@@ -18,17 +17,26 @@ in
{
options.my.services.matrix = with lib; {
enable = mkEnableOption "Matrix Synapse";
- secret = mkOption {
- type = types.str;
- example = "deadbeef";
+
+ secretFile = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ example = "/var/lib/matrix/shared-secret-config.yaml";
description = "Shared secret to register users";
};
+
+ mailConfigFile = mkOption {
+ type = types.str;
+ example = "/var/lib/matrix/email-config.yaml";
+ description = ''
+ Configuration file for mail setup.
+ '';
+ };
};
config = lib.mkIf cfg.enable {
services.postgresql = {
enable = true;
- package = pkgs.postgresql_12;
initialScript = pkgs.writeText "synapse-init.sql" ''
CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD 'synapse';
CREATE DATABASE "matrix-synapse" WITH OWNER "matrix-synapse"
@@ -41,36 +49,79 @@ in
services.matrix-synapse = {
enable = true;
dataDir = "/var/lib/matrix-synapse";
- server_name = domain;
- public_baseurl = "https://matrix.${domain}";
- enable_registration = false;
- registration_shared_secret = cfg.secret;
+ settings = {
+ server_name = domain;
+ public_baseurl = "https://matrix.${domain}";
- listeners = [
- # Federation
- {
- bind_address = "::1";
- port = federationPort.private;
- tls = false; # Terminated by nginx.
- x_forwarded = true;
- resources = [{ names = [ "federation" ]; compress = false; }];
- }
+ enable_registration = false;
- # Client
- {
- bind_address = "::1";
- port = clientPort.private;
- tls = false; # Terminated by nginx.
- x_forwarded = true;
- resources = [{ names = [ "client" ]; compress = false; }];
- }
- ];
+ listeners = [
+ # Federation
+ {
+ bind_addresses = [ "::1" ];
+ port = federationPort.private;
+ tls = false; # Terminated by nginx.
+ x_forwarded = true;
+ resources = [{ names = [ "federation" ]; compress = false; }];
+ }
+
+ # Client
+ {
+ bind_addresses = [ "::1" ];
+ port = clientPort.private;
+ tls = false; # Terminated by nginx.
+ x_forwarded = true;
+ resources = [{ names = [ "client" ]; compress = false; }];
+ }
+ ];
+
+ account_threepid_delegates = {
+ msisdn = "https://vector.im";
+ };
+
+ experimental_features = {
+ spaces_enabled = true;
+ };
+ };
+
+ extraConfigFiles = [
+ cfg.mailConfigFile
+ ] ++ lib.optional (cfg.secretFile != null) cfg.secretFile;
};
+ my.services.nginx.virtualHosts = [
+ # Element Web app deployment
+ {
+ subdomain = "chat";
+ root = pkgs.element-web.override {
+ conf = {
+ default_server_config = {
+ "m.homeserver" = {
+ "base_url" = "https://matrix.${domain}";
+ "server_name" = domain;
+ };
+ "m.identity_server" = {
+ "base_url" = "https://vector.im";
+ };
+ };
+ showLabsSettings = true;
+ defaultCountryCode = "FR"; # cocorico
+ roomDirectory = {
+ "servers" = [
+ "matrix.org"
+ "mozilla.org"
+ ];
+ };
+ };
+ };
+ }
+ ];
+
+ # Those are too complicated to use my wrapper...
services.nginx.virtualHosts = {
"matrix.${domain}" = {
- forceSSL = true;
+ onlySSL = true;
useACMEHost = domain;
locations =
@@ -97,8 +148,8 @@ in
};
# same as above, but listening on the federation port
- "matrix.${domain}_federation" = rec {
- forceSSL = true;
+ "matrix.${domain}_federation" = {
+ onlySSL = true;
serverName = "matrix.${domain}";
useACMEHost = domain;
@@ -112,7 +163,6 @@ in
{ addr = "0.0.0.0"; port = federationPort.public; ssl = true; }
{ addr = "[::]"; port = federationPort.public; ssl = true; }
];
-
};
"${domain}" = {
@@ -142,34 +192,6 @@ in
return 200 '${builtins.toJSON client}';
'';
};
-
- # Element Web app deployment
- "chat.${domain}" = {
- useACMEHost = domain;
- forceSSL = true;
-
- root = pkgs.element-web.override {
- conf = {
- default_server_config = {
- "m.homeserver" = {
- "base_url" = "https://matrix.${domain}";
- "server_name" = domain;
- };
- "m.identity_server" = {
- "base_url" = "https://vector.im";
- };
- };
- showLabsSettings = true;
- defaultCountryCode = "FR"; # cocorico
- roomDirectory = {
- "servers" = [
- "matrix.org"
- "mozilla.org"
- ];
- };
- };
- };
- };
};
# For administration tools.
diff --git a/modules/services/miniflux/default.nix b/modules/services/miniflux/default.nix
new file mode 100644
index 0000000..6d9ffc8
--- /dev/null
+++ b/modules/services/miniflux/default.nix
@@ -0,0 +1,53 @@
+# A minimalist, opinionated feed reader
+{ config, lib, ... }:
+let
+ cfg = config.my.services.miniflux;
+in
+{
+ options.my.services.miniflux = with lib; {
+ enable = mkEnableOption "Miniflux feed reader";
+
+ credentialsFiles = mkOption {
+ type = types.str;
+ example = "/var/lib/miniflux/creds.env";
+ description = ''
+ Credential file as an 'EnvironmentFile' (see `systemd.exec(5)`)
+ '';
+ };
+
+ port = mkOption {
+ type = types.port;
+ default = 9876;
+ example = 8080;
+ description = "Internal port for webui";
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ # The service automatically sets up the DB
+ services.miniflux = {
+ enable = true;
+
+ adminCredentialsFile = cfg.credentialsFiles;
+
+ config = {
+ # Virtual hosts settings
+ BASE_URL = "https://reader.${config.networking.domain}";
+ LISTEN_ADDR = "localhost:${toString cfg.port}";
+ # I want fast updates
+ POLLING_FREQUENCY = "30";
+ BATCH_SIZE = "50";
+ # I am a hoarder
+ CLEANUP_ARCHIVE_UNREAD_DAYS = "-1";
+ CLEANUP_ARCHIVE_READ_DAYS = "-1";
+ };
+ };
+
+ my.services.nginx.virtualHosts = [
+ {
+ subdomain = "reader";
+ inherit (cfg) port;
+ }
+ ];
+ };
+}
diff --git a/modules/services/monitoring/default.nix b/modules/services/monitoring/default.nix
new file mode 100644
index 0000000..829bfe0
--- /dev/null
+++ b/modules/services/monitoring/default.nix
@@ -0,0 +1,135 @@
+# Grafana dashboards for all the things!
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.my.services.monitoring;
+in
+{
+ options.my.services.monitoring = with lib; {
+ enable = mkEnableOption "monitoring";
+
+ grafana = {
+ port = mkOption {
+ type = types.port;
+ default = 9500;
+ example = 3001;
+ description = "Internal port";
+ };
+
+ username = mkOption {
+ type = types.str;
+ default = "ambroisie";
+ example = "admin";
+ description = "Admin username";
+ };
+
+ passwordFile = mkOption {
+ type = types.str;
+ example = "/var/lib/grafana/password.txt";
+ description = "Admin password stored in a file";
+ };
+
+ secretKeyFile = mkOption {
+ type = types.str;
+ example = "/var/lib/grafana/secret_key.txt";
+ description = "Secret key stored in a file";
+ };
+ };
+
+ prometheus = {
+ port = mkOption {
+ type = types.port;
+ default = 9501;
+ example = 3002;
+ description = "Internal port";
+ };
+
+ scrapeInterval = mkOption {
+ type = types.str;
+ default = "15s";
+ example = "1m";
+ description = "Scrape interval";
+ };
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ services.grafana = {
+ enable = true;
+
+ settings = {
+ server = {
+ domain = "monitoring.${config.networking.domain}";
+ root_url = "https://monitoring.${config.networking.domain}/";
+ http_port = cfg.grafana.port;
+ http_addr = "127.0.0.1"; # Proxied through Nginx
+ };
+
+ security = {
+ admin_user = cfg.grafana.username;
+ admin_password = "$__file{${cfg.grafana.passwordFile}}";
+ secret_key = "$__file{${cfg.grafana.secretKeyFile}}";
+ };
+ };
+
+ provision = {
+ enable = true;
+
+ datasources.settings.datasources = [
+ {
+ name = "Prometheus";
+ type = "prometheus";
+ url = "http://localhost:${toString cfg.prometheus.port}";
+ jsonData = {
+ timeInterval = cfg.prometheus.scrapeInterval;
+ };
+ }
+ ];
+
+ dashboards.settings.providers = [
+ {
+ name = "Node Exporter";
+ options.path = pkgs.nur.repos.alarsyo.grafanaDashboards.node-exporter;
+ disableDeletion = true;
+ }
+ ];
+ };
+ };
+
+ services.prometheus = {
+ enable = true;
+ port = cfg.prometheus.port;
+ listenAddress = "127.0.0.1";
+
+ retentionTime = "2y";
+
+ exporters = {
+ node = {
+ enable = true;
+ enabledCollectors = [ "systemd" ];
+ port = 9100;
+ listenAddress = "127.0.0.1";
+ };
+ };
+
+ globalConfig = {
+ scrape_interval = cfg.prometheus.scrapeInterval;
+ };
+
+ scrapeConfigs = [
+ {
+ job_name = config.networking.hostName;
+ static_configs = [{
+ targets = [ "127.0.0.1:${toString config.services.prometheus.exporters.node.port}" ];
+ }];
+ }
+ ];
+ };
+
+ my.services.nginx.virtualHosts = [
+ {
+ subdomain = "monitoring";
+ inherit (cfg.grafana) port;
+ }
+ ];
+ };
+}
diff --git a/modules/services/navidrome/default.nix b/modules/services/navidrome/default.nix
new file mode 100644
index 0000000..6c001fd
--- /dev/null
+++ b/modules/services/navidrome/default.nix
@@ -0,0 +1,57 @@
+# A FLOSS self-hosted, subsonic compatible music server
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.my.services.navidrome;
+in
+{
+ options.my.services.navidrome = with lib; {
+ enable = mkEnableOption "Navidrome Music Server";
+
+ settings = mkOption {
+ type = (pkgs.formats.json { }).type;
+ default = { };
+ example = {
+ "LastFM.ApiKey" = "MYKEY";
+ "LastFM.Secret" = "MYSECRET";
+ "Spotify.ID" = "MYKEY";
+ "Spotify.Secret" = "MYSECRET";
+ };
+ description = ''
+ Additional settings.
+ '';
+ };
+
+ port = mkOption {
+ type = types.port;
+ default = 4533;
+ example = 8080;
+ description = "Internal port for webui";
+ };
+
+ musicFolder = mkOption {
+ type = types.str;
+ example = "/mnt/music/";
+ description = "Music folder";
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ services.navidrome = {
+ enable = true;
+
+ settings = cfg.settings // {
+ Port = cfg.port;
+ Address = "127.0.0.1"; # Behind reverse proxy, so only loopback
+ MusicFolder = cfg.musicFolder;
+ LogLevel = "info";
+ };
+ };
+
+ my.services.nginx.virtualHosts = [
+ {
+ subdomain = "music";
+ inherit (cfg) port;
+ }
+ ];
+ };
+}
diff --git a/services/nextcloud.nix b/modules/services/nextcloud/default.nix
similarity index 59%
rename from services/nextcloud.nix
rename to modules/services/nextcloud/default.nix
index d52e32a..1477c13 100644
--- a/services/nextcloud.nix
+++ b/modules/services/nextcloud/default.nix
@@ -2,8 +2,6 @@
{ config, lib, pkgs, ... }:
let
cfg = config.my.services.nextcloud;
- domain = config.networking.domain;
- nextcloudDomain = "nextcloud.${config.networking.domain}";
in
{
options.my.services.nextcloud = with lib; {
@@ -20,27 +18,38 @@ in
example = "admin";
description = "Name of the admin user";
};
- password = mkOption {
+ passwordFile = mkOption {
type = types.str;
- example = "password";
- description = "The admin user's password";
+ example = "/var/lib/nextcloud/password.txt";
+ description = ''
+ Path to a file containing the admin's password, must be readable by
+ 'nextcloud' user.
+ '';
};
};
config = lib.mkIf cfg.enable {
services.nextcloud = {
enable = true;
- package = pkgs.nextcloud21;
- hostName = nextcloudDomain;
+ package = pkgs.nextcloud27;
+ hostName = "nextcloud.${config.networking.domain}";
home = "/var/lib/nextcloud";
maxUploadSize = cfg.maxSize;
+ enableBrokenCiphersForSSE = false;
+ configureRedis = true;
config = {
adminuser = cfg.admin;
- adminpass = cfg.password; # Insecure, but I don't care
+ adminpassFile = cfg.passwordFile;
dbtype = "pgsql";
dbhost = "/run/postgresql";
overwriteProtocol = "https"; # Nginx only allows SSL
};
+
+ notify_push = {
+ enable = true;
+ # Allow using the push service without hard-coding my IP in the configuration
+ bendDomainToLocalhost = true;
+ };
};
services.postgresql = {
@@ -59,17 +68,20 @@ in
after = [ "postgresql.service" ];
};
- services.nginx.virtualHosts."${nextcloudDomain}" = {
+ # The service above configures the domain, no need for my wrapper
+ services.nginx.virtualHosts."nextcloud.${config.networking.domain}" = {
forceSSL = true;
- useACMEHost = domain;
-
- locations."/".proxyPass = "http://127.0.0.1:3000/";
+ useACMEHost = config.networking.domain;
};
my.services.backup = {
paths = [
config.services.nextcloud.home
];
+ exclude = [
+ # image previews can take up a lot of space
+ "${config.services.nextcloud.home}/data/appdata_*/preview"
+ ];
};
};
}
diff --git a/modules/services/nginx/default.nix b/modules/services/nginx/default.nix
new file mode 100644
index 0000000..dcaaa0f
--- /dev/null
+++ b/modules/services/nginx/default.nix
@@ -0,0 +1,474 @@
+# A simple abstraction layer for almost all of my services' needs
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.my.services.nginx;
+
+ domain = config.networking.domain;
+
+ virtualHostOption = with lib; types.submodule {
+ options = {
+ subdomain = mkOption {
+ type = types.str;
+ example = "dev";
+ description = ''
+ Which subdomain, under config.networking.domain, to use
+ for this virtual host.
+ '';
+ };
+
+ port = mkOption {
+ type = with types; nullOr port;
+ default = null;
+ example = 8080;
+ description = ''
+ Which port to proxy to, through 127.0.0.1, for this virtual host.
+ This option is incompatible with `root`.
+ '';
+ };
+
+ redirect = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ example = "https://example.com";
+ description = ''
+ Which domain to redirect to (301 response), for this virtual host.
+ '';
+ };
+
+ root = mkOption {
+ type = with types; nullOr path;
+ default = null;
+ example = "/var/www/blog";
+ description = ''
+ The root folder for this virtual host. This option is incompatible
+ with `port`.
+ '';
+ };
+
+ socket = mkOption {
+ type = with types; nullOr path;
+ default = null;
+ example = "FIXME";
+ description = ''
+ The UNIX socket for this virtual host. This option is incompatible
+ with `port`.
+ '';
+ };
+
+ sso = {
+ enable = mkEnableOption "SSO authentication";
+ };
+
+ extraConfig = mkOption {
+ type = types.attrs; # FIXME: forward type of virtualHosts
+ example = litteralExample ''
+ {
+ locations."/socket" = {
+ proxyPass = "http://127.0.0.1:8096/";
+ proxyWebsockets = true;
+ };
+ }
+ '';
+ default = { };
+ description = ''
+ Any extra configuration that should be applied to this virtual host.
+ '';
+ };
+ };
+ };
+in
+{
+ imports = [
+ ./sso
+ ];
+
+ options.my.services.nginx = with lib; {
+ enable = mkEnableOption "Nginx";
+
+ acme = {
+ credentialsFile = mkOption {
+ type = types.str;
+ example = "/var/lib/acme/creds.env";
+ description = ''
+ Gandi API key file as an 'EnvironmentFile' (see `systemd.exec(5)`)
+ '';
+ };
+ };
+
+ monitoring = {
+ enable = my.mkDisableOption "monitoring through grafana and prometheus";
+ };
+
+ virtualHosts = mkOption {
+ type = types.listOf virtualHostOption;
+ default = [ ];
+ example = litteralExample ''
+ [
+ {
+ subdomain = "gitea";
+ port = 8080;
+ }
+ {
+ subdomain = "dev";
+ root = "/var/www/dev";
+ }
+ {
+ subdomain = "jellyfin";
+ port = 8096;
+ extraConfig = {
+ locations."/socket" = {
+ proxyPass = "http://127.0.0.1:8096/";
+ proxyWebsockets = true;
+ };
+ };
+ }
+ ]
+ '';
+ description = ''
+ List of virtual hosts to set-up using default settings.
+ '';
+ };
+
+ sso = {
+ authKeyFile = mkOption {
+ type = types.str;
+ example = "/var/lib/nginx-sso/auth-key.txt";
+ description = ''
+ Path to the auth key.
+ '';
+ };
+
+ subdomain = mkOption {
+ type = types.str;
+ default = "login";
+ example = "auth";
+ description = "Which subdomain, to use for SSO.";
+ };
+
+ port = mkOption {
+ type = types.port;
+ default = 8082;
+ example = 8080;
+ description = "Port to use for internal webui.";
+ };
+
+ users = mkOption {
+ type = types.attrsOf (types.submodule {
+ options = {
+ passwordHashFile = mkOption {
+ type = types.str;
+ example = "/var/lib/nginx-sso/alice/password-hash.txt";
+ description = "Path to file containing the user's password hash.";
+ };
+ totpSecretFile = mkOption {
+ type = types.str;
+ example = "/var/lib/nginx-sso/alice/totp-secret.txt";
+ description = "Path to file containing the user's TOTP secret.";
+ };
+ };
+ });
+ example = litteralExample ''
+ {
+ alice = {
+ passwordHashFile = "/var/lib/nginx-sso/alice/password-hash.txt";
+ totpSecretFile = "/var/lib/nginx-sso/alice/totp-secret.txt";
+ };
+ }
+ '';
+ description = "Definition of users";
+ };
+
+ groups = mkOption {
+ type = with types; attrsOf (listOf str);
+ example = litteralExample ''
+ {
+ root = [ "alice" ];
+ users = [ "alice" "bob" ];
+ }
+ '';
+ description = "Groups of users";
+ };
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ assertions = [ ]
+ ++ (lib.flip builtins.map cfg.virtualHosts ({ subdomain, ... } @ args:
+ let
+ conflicts = [ "port" "root" "socket" "redirect" ];
+ optionsNotNull = builtins.map (v: args.${v} != null) conflicts;
+ optionsSet = lib.filter lib.id optionsNotNull;
+ in
+ {
+ assertion = builtins.length optionsSet == 1;
+ message = ''
+ Subdomain '${subdomain}' must have exactly one of ${
+ lib.concatStringsSep ", " (builtins.map (v: "'${v}'") conflicts)
+ } configured.
+ '';
+ }))
+ ++ (
+ let
+ ports = lib.my.mapFilter
+ (v: v != null)
+ ({ port, ... }: port)
+ cfg.virtualHosts;
+ portCounts = lib.my.countValues ports;
+ nonUniquesCounts = lib.filterAttrs (_: v: v != 1) portCounts;
+ nonUniques = builtins.attrNames nonUniquesCounts;
+ mkAssertion = port: {
+ assertion = false;
+ message = "Port ${port} cannot appear in multiple virtual hosts.";
+ };
+ in
+ map mkAssertion nonUniques
+ ) ++ (
+ let
+ subs = map ({ subdomain, ... }: subdomain) cfg.virtualHosts;
+ subsCounts = lib.my.countValues subs;
+ nonUniquesCounts = lib.filterAttrs (_: v: v != 1) subsCounts;
+ nonUniques = builtins.attrNames nonUniquesCounts;
+ mkAssertion = v: {
+ assertion = false;
+ message = ''
+ Subdomain '${v}' cannot appear in multiple virtual hosts.
+ '';
+ };
+ in
+ map mkAssertion nonUniques
+ )
+ ;
+
+ services.nginx = {
+ enable = true;
+ statusPage = true; # For monitoring scraping.
+
+ recommendedBrotliSettings = true;
+ recommendedGzipSettings = true;
+ recommendedOptimisation = true;
+ recommendedProxySettings = true;
+ recommendedTlsSettings = true;
+ recommendedZstdSettings = true;
+
+ virtualHosts =
+ let
+ domain = config.networking.domain;
+ mkVHost = ({ subdomain, ... } @ args: lib.nameValuePair
+ "${subdomain}.${domain}"
+ (lib.my.recursiveMerge [
+ # Base configuration
+ {
+ forceSSL = true;
+ useACMEHost = domain;
+ }
+ # Proxy to port
+ (lib.optionalAttrs (args.port != null) {
+ locations."/".proxyPass =
+ "http://127.0.0.1:${toString args.port}";
+ })
+ # Serve filesystem content
+ (lib.optionalAttrs (args.root != null) {
+ inherit (args) root;
+ })
+ # Serve to UNIX socket
+ (lib.optionalAttrs (args.socket != null) {
+ locations."/".proxyPass =
+ "http://unix:${args.socket}";
+ })
+ # Redirect to a different domain
+ (lib.optionalAttrs (args.redirect != null) {
+ locations."/".return = "301 ${args.redirect}$request_uri";
+ })
+ # VHost specific configuration
+ args.extraConfig
+ # SSO configuration
+ (lib.optionalAttrs args.sso.enable {
+ extraConfig = (args.extraConfig.extraConfig or "") + ''
+ error_page 401 = @error401;
+ '';
+
+ locations."@error401".return = ''
+ 302 https://${cfg.sso.subdomain}.${config.networking.domain}/login?go=$scheme://$http_host$request_uri
+ '';
+
+ locations."/" = {
+ extraConfig =
+ (args.extraConfig.locations."/".extraConfig or "") + ''
+ # Use SSO
+ auth_request /sso-auth;
+
+ # Set username through header
+ auth_request_set $username $upstream_http_x_username;
+ proxy_set_header X-User $username;
+
+ # Renew SSO cookie on request
+ auth_request_set $cookie $upstream_http_set_cookie;
+ add_header Set-Cookie $cookie;
+ '';
+ };
+
+ locations."/sso-auth" = {
+ proxyPass = "http://localhost:${toString cfg.sso.port}/auth";
+ extraConfig = ''
+ # Do not allow requests from outside
+ internal;
+
+ # Do not forward the request body
+ proxy_pass_request_body off;
+ proxy_set_header Content-Length "";
+
+ # Set X-Application according to subdomain for matching
+ proxy_set_header X-Application "${subdomain}";
+
+ # Set origin URI for matching
+ proxy_set_header X-Origin-URI $request_uri;
+ '';
+ };
+ })
+ ])
+ );
+ in
+ lib.my.genAttrs' cfg.virtualHosts mkVHost;
+
+ sso = {
+ enable = true;
+
+ configuration = {
+ listen = {
+ addr = "127.0.0.1";
+ inherit (cfg.sso) port;
+ };
+
+ audit_log = {
+ target = [
+ "fd://stdout"
+ ];
+ events = [
+ "access_denied"
+ "login_success"
+ "login_failure"
+ "logout"
+ "validate"
+ ];
+ headers = [
+ "x-origin-uri"
+ "x-application"
+ ];
+ };
+
+ cookie = {
+ domain = ".${config.networking.domain}";
+ secure = true;
+ authentication_key = {
+ _secret = cfg.sso.authKeyFile;
+ };
+ };
+
+ login = {
+ title = "Ambroisie's SSO";
+ default_method = "simple";
+ hide_mfa_field = false;
+ names = {
+ simple = "Username / Password";
+ };
+ };
+
+ providers = {
+ simple =
+ let
+ applyUsers = lib.flip lib.mapAttrs cfg.sso.users;
+ in
+ {
+ users = applyUsers (_: v: { _secret = v.passwordHashFile; });
+
+ mfa = applyUsers (_: v: [{
+ provider = "totp";
+ attributes = {
+ secret = {
+ _secret = v.totpSecretFile;
+ };
+ };
+ }]);
+
+ inherit (cfg.sso) groups;
+ };
+ };
+
+ acl = {
+ rule_sets = [
+ {
+ rules = [{ field = "x-application"; present = true; }];
+ allow = [ "@root" ];
+ }
+ ];
+ };
+ };
+ };
+ };
+
+ my.services.nginx.virtualHosts = [
+ {
+ subdomain = "login";
+ inherit (cfg.sso) port;
+ }
+ ];
+
+ networking.firewall.allowedTCPPorts = [ 80 443 ];
+
+ # Nginx needs to be able to read the certificates
+ users.users.nginx.extraGroups = [ "acme" ];
+
+ security.acme = {
+ defaults.email = lib.my.mkMailAddress "bruno.acme" "belanyi.fr";
+
+ acceptTerms = true;
+ # Use DNS wildcard certificate
+ certs =
+ {
+ "${domain}" = {
+ extraDomainNames = [ "*.${domain}" ];
+ dnsProvider = "gandiv5";
+ inherit (cfg.acme) credentialsFile;
+ };
+ };
+ };
+
+ systemd.services."acme-${domain}" = {
+ serviceConfig = {
+ Environment = [
+ # Since I do a "weird" setup with a wildcard CNAME
+ "LEGO_DISABLE_CNAME_SUPPORT=true"
+ ];
+ };
+ };
+
+ services.grafana.provision.dashboards.settings.providers = lib.mkIf cfg.monitoring.enable [
+ {
+ name = "NGINX";
+ options.path = pkgs.nur.repos.alarsyo.grafanaDashboards.nginx;
+ disableDeletion = true;
+ }
+ ];
+
+ services.prometheus = lib.mkIf cfg.monitoring.enable {
+ exporters.nginx = {
+ enable = true;
+ listenAddress = "127.0.0.1";
+ };
+
+ scrapeConfigs = [
+ {
+ job_name = "nginx";
+ static_configs = [
+ {
+ targets = [ "127.0.0.1:${toString config.services.prometheus.exporters.nginx.port}" ];
+ labels = {
+ instance = config.networking.hostName;
+ };
+ }
+ ];
+ }
+ ];
+ };
+ };
+}
diff --git a/modules/services/nginx/sso/default.nix b/modules/services/nginx/sso/default.nix
new file mode 100644
index 0000000..4a78282
--- /dev/null
+++ b/modules/services/nginx/sso/default.nix
@@ -0,0 +1,89 @@
+# I must override the module to allow having runtime secrets
+{ config, lib, pkgs, utils, ... }:
+let
+ cfg = config.services.nginx.sso;
+ pkg = lib.getBin cfg.package;
+ confPath = "/var/lib/nginx-sso/config.json";
+in
+{
+ disabledModules = [ "services/security/nginx-sso.nix" ];
+
+
+ options.services.nginx.sso = with lib; {
+ enable = mkEnableOption "nginx-sso service";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.nginx-sso;
+ defaultText = "pkgs.nginx-sso";
+ description = ''
+ The nginx-sso package that should be used.
+ '';
+ };
+
+ configuration = mkOption {
+ type = types.attrsOf types.unspecified;
+ default = { };
+ example = literalExample ''
+ {
+ listen = { addr = "127.0.0.1"; port = 8080; };
+
+ providers.token.tokens = {
+ myuser = "MyToken";
+ };
+
+ acl = {
+ rule_sets = [
+ {
+ rules = [ { field = "x-application"; equals = "MyApp"; } ];
+ allow = [ "myuser" ];
+ }
+ ];
+ };
+ }
+ '';
+ description = ''
+ nginx-sso configuration
+ (documentation)
+ as a Nix attribute set.
+ '';
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ systemd.services.nginx-sso = {
+ description = "Nginx SSO Backend";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ StateDirectory = "nginx-sso";
+ WorkingDirectory = "/var/lib/nginx-sso";
+ # The files to be merged might not have the correct permissions
+ ExecStartPre = ''+${pkgs.writeShellScript "merge-nginx-sso-config" ''
+ rm -f '${confPath}'
+ ${utils.genJqSecretsReplacementSnippet cfg.configuration confPath}
+
+ # Fix permissions
+ chown nginx-sso:nginx-sso ${confPath}
+ chmod 0600 ${confPath}
+ ''
+ }'';
+ ExecStart = lib.mkForce ''
+ ${lib.getExe pkg} \
+ --config ${confPath} \
+ --frontend-dir ${pkg}/share/frontend
+ '';
+ Restart = "always";
+ User = "nginx-sso";
+ Group = "nginx-sso";
+ };
+ };
+
+ users.users.nginx-sso = {
+ isSystemUser = true;
+ group = "nginx-sso";
+ };
+
+ users.groups.nginx-sso = { };
+ };
+}
diff --git a/modules/services/nix-serve/default.nix b/modules/services/nix-serve/default.nix
new file mode 100644
index 0000000..0cf1573
--- /dev/null
+++ b/modules/services/nix-serve/default.nix
@@ -0,0 +1,57 @@
+# Binary cache through nix-serve
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.my.services.nix-serve;
+in
+{
+ options.my.services.nix-serve = with lib; {
+ enable = mkEnableOption "nix-serve binary cache";
+
+ port = mkOption {
+ type = types.port;
+ default = 5000;
+ example = 8080;
+ description = "Internal port for serving cache";
+ };
+
+ secretKeyFile = mkOption {
+ type = types.str;
+ example = "/run/secrets/nix-serve";
+ description = "Secret signing key for the cache";
+ };
+
+ priority = mkOption {
+ type = types.int;
+ default = 50;
+ example = 30;
+ description = ''
+ Which priority to assign to this cache. Lower number is higher priority.
+ The official nixpkgs hydra cache is priority 40.
+ '';
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ services.nix-serve = {
+ enable = true;
+
+ bindAddress = "127.0.0.1";
+
+ inherit (cfg)
+ port
+ secretKeyFile
+ ;
+
+ package = pkgs.nix-serve-ng;
+
+ extraParams = "--priority=${toString cfg.priority}";
+ };
+
+ my.services.nginx.virtualHosts = [
+ {
+ subdomain = "cache";
+ inherit (cfg) port;
+ }
+ ];
+ };
+}
diff --git a/modules/services/paperless/default.nix b/modules/services/paperless/default.nix
new file mode 100644
index 0000000..c9d6220
--- /dev/null
+++ b/modules/services/paperless/default.nix
@@ -0,0 +1,164 @@
+{ config, lib, ... }:
+let
+ cfg = config.my.services.paperless;
+in
+{
+ options.my.services.paperless = with lib; {
+ enable = mkEnableOption "Paperless service";
+
+ port = mkOption {
+ type = types.port;
+ default = 4535;
+ example = 8080;
+ description = "Internal port for webui";
+ };
+
+ secretKeyFile = mkOption {
+ type = types.str;
+ example = "/var/lib/paperless/secret-key.env";
+ description = ''
+ Secret key as an 'EnvironmentFile' (see `systemd.exec(5)`)
+ '';
+ };
+
+ documentPath = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ example = "/mnt/paperless";
+ description = ''
+ Path to the directory to store the documents. Use default if null
+ '';
+ };
+
+ username = mkOption {
+ type = types.str;
+ default = "ambroisie";
+ example = "username";
+ description = "Name of the administrator";
+ };
+
+ passwordFile = mkOption {
+ type = types.str;
+ example = "/var/lib/paperless/password.txt";
+ description = "Read the administrator's password from this path";
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ services.paperless = {
+ enable = true;
+
+ port = cfg.port;
+
+ mediaDir = lib.mkIf (cfg.documentPath != null) cfg.documentPath;
+
+ extraConfig =
+ let
+ paperlessDomain = "paperless.${config.networking.domain}";
+ in
+ {
+ # Use SSO
+ PAPERLESS_ENABLE_HTTP_REMOTE_USER = true;
+ PAPERLESS_HTTP_REMOTE_USER_HEADER_NAME = "HTTP_X_USER";
+
+ # Use PostgreSQL
+ PAPERLESS_DBHOST = "/run/postgresql";
+ PAPERLESS_DBUSER = "paperless";
+ PAPERLESS_DBNAME = "paperless";
+
+ # Security settings
+ PAPERLESS_ALLOWED_HOSTS = paperlessDomain;
+ PAPERLESS_CORS_ALLOWED_HOSTS = "https://${paperlessDomain}";
+
+ # OCR settings
+ PAPERLESS_OCR_LANGUAGE = "fra+eng";
+
+ # Misc
+ PAPERLESS_TIME_ZONE = config.time.timeZone;
+ PAPERLESS_ADMIN_USER = cfg.username;
+ };
+
+ # Admin password
+ passwordFile = cfg.passwordFile;
+ };
+
+ systemd.services = {
+ paperless-scheduler = {
+ requires = [ "postgresql.service" ];
+ after = [ "postgresql.service" ];
+
+ serviceConfig = {
+ EnvironmentFile = cfg.secretKeyFile;
+ };
+ };
+
+ paperless-consumer = {
+ requires = [ "postgresql.service" ];
+ after = [ "postgresql.service" ];
+
+ serviceConfig = {
+ EnvironmentFile = cfg.secretKeyFile;
+ };
+ };
+
+ paperless-web = {
+ requires = [ "postgresql.service" ];
+ after = [ "postgresql.service" ];
+
+ serviceConfig = {
+ EnvironmentFile = cfg.secretKeyFile;
+ };
+ };
+
+ paperless-task-queue = {
+ requires = [ "postgresql.service" ];
+ after = [ "postgresql.service" ];
+
+ serviceConfig = {
+ EnvironmentFile = cfg.secretKeyFile;
+ };
+ };
+ };
+
+ # Set-up database
+ services.postgresql = {
+ enable = true;
+ ensureDatabases = [ "paperless" ];
+ ensureUsers = [
+ {
+ name = "paperless";
+ ensurePermissions."DATABASE paperless" = "ALL PRIVILEGES";
+ }
+ ];
+ };
+
+ # Set-up media group
+ users.groups.media = { };
+
+ users.users.${config.services.paperless.user} = {
+ extraGroups = [ "media" ];
+ };
+
+ my.services.nginx.virtualHosts = [
+ {
+ subdomain = "paperless";
+ inherit (cfg) port;
+ sso = {
+ enable = true;
+ };
+
+ # Enable websockets on root
+ extraConfig = {
+ locations."/".proxyWebsockets = true;
+ };
+ }
+ ];
+
+ my.services.backup = {
+ paths = [
+ config.services.paperless.dataDir
+ config.services.paperless.mediaDir
+ ];
+ };
+ };
+}
diff --git a/services/pirate.nix b/modules/services/pirate/default.nix
similarity index 58%
rename from services/pirate.nix
rename to modules/services/pirate/default.nix
index 2eb490b..42dd12b 100644
--- a/services/pirate.nix
+++ b/modules/services/pirate/default.nix
@@ -5,13 +5,12 @@
{ config, lib, ... }:
let
cfg = config.my.services.pirate;
- domain = config.networking.domain;
ports = {
- sonarr = 8989;
- radarr = 7878;
bazarr = 6767;
lidarr = 8686;
+ radarr = 7878;
+ sonarr = 8989;
};
managers = with lib.attrsets;
@@ -22,15 +21,8 @@ let
})
ports);
- redirections = with lib.attrsets;
- (mapAttrs'
- (service: port: nameValuePair "${service}.${domain}" {
- forceSSL = true;
- useACMEHost = domain;
-
- locations."/".proxyPass = "http://127.0.0.1:${builtins.toString port}/";
- })
- ports);
+ redirections = lib.flip lib.mapAttrsToList ports
+ (subdomain: port: { inherit subdomain port; });
in
{
options.my.services.pirate = {
@@ -38,6 +30,9 @@ in
};
config = lib.mkIf cfg.enable {
- services = managers // { nginx.virtualHosts = redirections; };
+ services = managers;
+ my.services.nginx.virtualHosts = redirections;
+ # Set-up media group
+ users.groups.media = { };
};
}
diff --git a/modules/services/podgrab/default.nix b/modules/services/podgrab/default.nix
new file mode 100644
index 0000000..9793d60
--- /dev/null
+++ b/modules/services/podgrab/default.nix
@@ -0,0 +1,41 @@
+# A simple podcast fetcher
+{ config, lib, ... }:
+let
+ cfg = config.my.services.podgrab;
+in
+{
+ options.my.services.podgrab = with lib; {
+ enable = mkEnableOption "Podgrab, a self-hosted podcast manager";
+
+ passwordFile = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ example = "/run/secrets/password.env";
+ description = ''
+ The path to a file containing the PASSWORD environment variable
+ definition for Podgrab's authentification.
+ '';
+ };
+
+ port = mkOption {
+ type = types.port;
+ default = 8080;
+ example = 4242;
+ description = "The port on which Podgrab will listen for incoming HTTP traffic.";
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ services.podgrab = {
+ enable = true;
+ inherit (cfg) passwordFile port;
+ };
+
+ my.services.nginx.virtualHosts = [
+ {
+ subdomain = "podgrab";
+ inherit (cfg) port;
+ }
+ ];
+ };
+}
diff --git a/services/postgresql-backup.nix b/modules/services/postgresql-backup/default.nix
similarity index 100%
rename from services/postgresql-backup.nix
rename to modules/services/postgresql-backup/default.nix
diff --git a/modules/services/postgresql/default.nix b/modules/services/postgresql/default.nix
new file mode 100644
index 0000000..6f51f3e
--- /dev/null
+++ b/modules/services/postgresql/default.nix
@@ -0,0 +1,61 @@
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.my.services.postgresql;
+in
+{
+ options.my.services.postgresql = with lib; {
+ enable = my.mkDisableOption "postgres configuration";
+
+ # Transient option to be enabled for migrations
+ upgradeScript = mkEnableOption "postgres upgrade script";
+ };
+
+ config = lib.mkMerge [
+ # Let other services enable postgres when they need it
+ (lib.mkIf cfg.enable {
+ services.postgresql = {
+ package = pkgs.postgresql_13;
+ };
+ })
+
+ # Taken from the manual
+ (lib.mkIf cfg.upgradeScript {
+ containers.temp-pg.config.services.postgresql = {
+ enable = true;
+ package = pkgs.postgresql_13;
+ };
+
+ environment.systemPackages =
+ let
+ newpg = config.containers.temp-pg.config.services.postgresql;
+ in
+ [
+ (pkgs.writeScriptBin "upgrade-pg-cluster" ''
+ #!/usr/bin/env bash
+
+ set -x
+ export OLDDATA="${config.services.postgresql.dataDir}"
+ export NEWDATA="${newpg.dataDir}"
+ export OLDBIN="${config.services.postgresql.package}/bin"
+ export NEWBIN="${newpg.package}/bin"
+
+ if [ "$OLDDATA" -ef "$NEWDATA" ]; then
+ echo "Cannot migrate to same data directory" >&2
+ exit 1
+ fi
+
+ install -d -m 0700 -o postgres -g postgres "$NEWDATA"
+ cd "$NEWDATA"
+ sudo -u postgres $NEWBIN/initdb -D "$NEWDATA"
+
+ systemctl stop postgresql # old one
+
+ sudo -u postgres $NEWBIN/pg_upgrade \
+ --old-datadir "$OLDDATA" --new-datadir "$NEWDATA" \
+ --old-bindir $OLDBIN --new-bindir $NEWBIN \
+ "$@"
+ '')
+ ];
+ })
+ ];
+}
diff --git a/services/quassel.nix b/modules/services/quassel/default.nix
similarity index 100%
rename from services/quassel.nix
rename to modules/services/quassel/default.nix
diff --git a/services/rss-bridge.nix b/modules/services/rss-bridge/default.nix
similarity index 60%
rename from services/rss-bridge.nix
rename to modules/services/rss-bridge/default.nix
index ad5141b..85e37c2 100644
--- a/services/rss-bridge.nix
+++ b/modules/services/rss-bridge/default.nix
@@ -2,8 +2,6 @@
{ config, lib, ... }:
let
cfg = config.my.services.rss-bridge;
- domain = config.networking.domain;
- rss-bridgeDomain = "rss-bridge.${config.networking.domain}";
in
{
options.my.services.rss-bridge = {
@@ -14,12 +12,13 @@ in
services.rss-bridge = {
enable = true;
whitelist = [ "*" ]; # Whitelist all
- virtualHost = rss-bridgeDomain; # Setup virtual host
+ virtualHost = "rss-bridge.${config.networking.domain}";
};
- services.nginx.virtualHosts."${rss-bridgeDomain}" = {
+ # The service above configures the domain, no need for my wrapper
+ services.nginx.virtualHosts."rss-bridge.${config.networking.domain}" = {
forceSSL = true;
- useACMEHost = domain;
+ useACMEHost = config.networking.domain;
};
};
}
diff --git a/modules/services/sabnzbd/default.nix b/modules/services/sabnzbd/default.nix
new file mode 100644
index 0000000..7ab145f
--- /dev/null
+++ b/modules/services/sabnzbd/default.nix
@@ -0,0 +1,57 @@
+# Usenet binary client.
+{ config, lib, ... }:
+let
+ cfg = config.my.services.sabnzbd;
+ port = 9090; # NOTE: not declaratively set...
+in
+{
+ options.my.services.sabnzbd = with lib; {
+ enable = mkEnableOption "SABnzbd binary news reader";
+ };
+
+ config = lib.mkIf cfg.enable {
+ services.sabnzbd = {
+ enable = true;
+ group = "media";
+ };
+
+ # Set-up media group
+ users.groups.media = { };
+
+ my.services.nginx.virtualHosts = [
+ {
+ subdomain = "sabnzbd";
+ inherit port;
+ }
+ ];
+
+ services.fail2ban.jails = {
+ sabnzbd = ''
+ enabled = true
+ filter = sabnzbd
+ port = http,https
+ # Unfortunately, sabnzbd does not log to systemd journal
+ backend = auto
+ logpath = /var/lib/sabnzbd/logs/sabnzbd.log
+ '';
+ };
+
+ environment.etc = {
+ # FIXME: path to log file
+ "fail2ban/filter.d/sabnzbd.conf".text = ''
+ [Definition]
+ failregex = ^.*WARNING.*API Key incorrect, Use the api key from Config->General in your 3rd party program: .* \(X-Forwarded-For: \) .*$
+ ^.*WARNING.*API Key incorrect, Use the api key from Config->General in your 3rd party program: .*$
+ ^.*WARNING.*API Key missing, please enter the api key from Config->General into your 3rd party program: .* \(X-Forwarded-For: \) .*$
+ ^.*WARNING.*API Key missing, please enter the api key from Config->General into your 3rd party program: .*$
+ ^.*WARNING.*Refused connection from: .* \(X-Forwarded-For: \) .*$
+ ^.*WARNING.*Refused connection from: .*$
+ ^.*WARNING.*Refused connection with hostname ".*" from: .* \(X-Forwarded-For: \) .*$
+ ^.*WARNING.*Refused connection with hostname ".*" from: .*$
+ ^.*WARNING.*Unsuccessful login attempt from .* \(X-Forwarded-For: \) .*$
+ ^.*WARNING.*Unsuccessful login attempt from .*$
+ journalmatch = _SYSTEMD_UNIT=sabnzbd.service
+ '';
+ };
+ };
+}
diff --git a/services/ssh-server.nix b/modules/services/ssh-server/default.nix
similarity index 76%
rename from services/ssh-server.nix
rename to modules/services/ssh-server/default.nix
index a41a673..9ae0fa8 100644
--- a/services/ssh-server.nix
+++ b/modules/services/ssh-server/default.nix
@@ -12,9 +12,12 @@ in
services.openssh = {
# Enable the OpenSSH daemon.
enable = true;
- # Be more secure
- permitRootLogin = "no";
- passwordAuthentication = false;
+
+ settings = {
+ # Be more secure
+ PermitRootLogin = "no";
+ PasswordAuthentication = false;
+ };
};
# Opens the relevant UDP ports.
diff --git a/modules/services/tandoor-recipes/default.nix b/modules/services/tandoor-recipes/default.nix
new file mode 100644
index 0000000..d78bef3
--- /dev/null
+++ b/modules/services/tandoor-recipes/default.nix
@@ -0,0 +1,79 @@
+{ config, lib, ... }:
+let
+ cfg = config.my.services.tandoor-recipes;
+in
+{
+ options.my.services.tandoor-recipes = with lib; {
+ enable = mkEnableOption "Tandoor Recipes service";
+
+ port = mkOption {
+ type = types.port;
+ default = 4536;
+ example = 8080;
+ description = "Internal port for webui";
+ };
+
+ secretKeyFile = mkOption {
+ type = types.str;
+ example = "/var/lib/tandoor-recipes/secret-key.env";
+ description = ''
+ Secret key as an 'EnvironmentFile' (see `systemd.exec(5)`)
+ '';
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ services.tandoor-recipes = {
+ enable = true;
+
+ port = cfg.port;
+ extraConfig =
+ let
+ tandoorRecipesDomain = "recipes.${config.networking.domain}";
+ in
+ {
+ # Use PostgreSQL
+ DB_ENGINE = "django.db.backends.postgresql";
+ POSTGRES_HOST = "/run/postgresql";
+ POSTGRES_USER = "tandoor_recipes";
+ POSTGRES_DB = "tandoor_recipes";
+
+ # Security settings
+ ALLOWED_HOSTS = tandoorRecipesDomain;
+ CSRF_TRUSTED_ORIGINS = "https://${tandoorRecipesDomain}";
+
+ # Misc
+ TIMEZONE = config.time.timeZone;
+ };
+ };
+
+ systemd.services = {
+ tandoor-recipes = {
+ after = [ "postgresql.service" ];
+
+ serviceConfig = {
+ EnvironmentFile = cfg.secretKeyFile;
+ };
+ };
+ };
+
+ # Set-up database
+ services.postgresql = {
+ enable = true;
+ ensureDatabases = [ "tandoor_recipes" ];
+ ensureUsers = [
+ {
+ name = "tandoor_recipes";
+ ensurePermissions."DATABASE tandoor_recipes" = "ALL PRIVILEGES";
+ }
+ ];
+ };
+
+ my.services.nginx.virtualHosts = [
+ {
+ subdomain = "recipes";
+ inherit (cfg) port;
+ }
+ ];
+ };
+}
diff --git a/modules/services/tlp/default.nix b/modules/services/tlp/default.nix
new file mode 100644
index 0000000..8c9edd6
--- /dev/null
+++ b/modules/services/tlp/default.nix
@@ -0,0 +1,26 @@
+# TLP power management
+{ config, lib, ... }:
+let
+ cfg = config.my.services.tlp;
+in
+{
+ options.my.services.tlp = {
+ enable = lib.mkEnableOption "TLP power management configuration";
+ };
+
+ config = lib.mkIf cfg.enable {
+ services.tlp = {
+ enable = true;
+
+ settings = {
+ # Set CPU scaling aggressively when power is not an issue
+ CPU_SCALING_GOVERNOR_ON_AC = "performance";
+ CPU_SCALING_GOVERNOR_ON_BAT = "powersave";
+
+ # Keep charge between 60% and 80% to preserve battery life
+ START_CHARGE_THRESH_BAT0 = 60;
+ STOP_CHARGE_THRESH_BAT0 = 80;
+ };
+ };
+ };
+}
diff --git a/services/transmission.nix b/modules/services/transmission/default.nix
similarity index 68%
rename from services/transmission.nix
rename to modules/services/transmission/default.nix
index 29e181b..dcba0aa 100644
--- a/services/transmission.nix
+++ b/modules/services/transmission/default.nix
@@ -6,25 +6,18 @@
{ config, lib, ... }:
let
cfg = config.my.services.transmission;
-
- domain = config.networking.domain;
- webuiDomain = "transmission.${domain}";
in
{
options.my.services.transmission = with lib; {
enable = mkEnableOption "Transmission torrent client";
- username = mkOption {
+ credentialsFile = mkOption {
type = types.str;
- default = "Ambroisie";
- example = "username";
- description = "Name of the transmission RPC user";
- };
-
- password = mkOption {
- type = types.str;
- example = "password";
- description = "Password of the transmission RPC user";
+ example = "/var/lib/transmission/creds.json";
+ description = ''
+ Credential file as an json configuration file to be merged with
+ the main one.
+ '';
};
downloadBase = mkOption {
@@ -34,7 +27,7 @@ in
description = "Download base directory";
};
- privatePort = mkOption {
+ port = mkOption {
type = types.port;
default = 9091;
example = 8080;
@@ -56,6 +49,8 @@ in
downloadDirPermissions = "775";
+ inherit (cfg) credentialsFile;
+
settings = {
download-dir = "${cfg.downloadBase}/complete";
incomplete-dir = "${cfg.downloadBase}/incomplete";
@@ -63,26 +58,33 @@ in
peer-port = cfg.peerPort;
rpc-enabled = true;
- rpc-port = cfg.privatePort;
+ rpc-port = cfg.port;
rpc-authentication-required = true;
- rpc-username = cfg.username;
- rpc-password = cfg.password; # Insecure, but I don't care.
-
# Proxied behind Nginx.
rpc-whitelist-enabled = true;
rpc-whitelist = "127.0.0.1";
};
};
+ # Transmission wants to eat *all* my RAM if left to its own devices
+ systemd.services.transmission = {
+ serviceConfig = {
+ MemoryMax = "33%";
+ };
+ };
+
+ # Set-up media group
+ users.groups.media = { };
+
# Default transmission webui, I prefer combustion but its development
# seems to have stalled
- services.nginx.virtualHosts."${webuiDomain}" = {
- forceSSL = true;
- useACMEHost = domain;
-
- locations."/".proxyPass = "http://127.0.0.1:${toString cfg.privatePort}";
- };
+ my.services.nginx.virtualHosts = [
+ {
+ subdomain = "transmission";
+ inherit (cfg) port;
+ }
+ ];
networking.firewall = {
allowedTCPPorts = [ cfg.peerPort ];
diff --git a/modules/services/vikunja/default.nix b/modules/services/vikunja/default.nix
new file mode 100644
index 0000000..1cdef5f
--- /dev/null
+++ b/modules/services/vikunja/default.nix
@@ -0,0 +1,123 @@
+# Todo and kanban app
+{ config, lib, ... }:
+let
+ cfg = config.my.services.vikunja;
+ subdomain = "todo";
+ vikunjaDomain = "${subdomain}.${config.networking.domain}";
+ socketPath = "/run/vikunja/vikunja.socket";
+in
+{
+ options.my.services.vikunja = with lib; {
+ enable = mkEnableOption "Vikunja todo app";
+
+ mail = {
+ enable = mkEnableOption {
+ description = "mailer configuration";
+ };
+
+ configFile = mkOption {
+ type = types.str;
+ example = "/run/secrets/vikunja-mail-config.env";
+ description = "Configuration for the mailer connection, using environment variables.";
+ };
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ services.vikunja = {
+ enable = true;
+
+ frontendScheme = "https";
+ frontendHostname = vikunjaDomain;
+
+ setupNginx = false;
+
+ database = {
+ type = "postgres";
+ user = "vikunja";
+ database = "vikunja";
+ host = "/run/postgresql";
+ };
+
+ settings = {
+ service = {
+ # Only allow registration of users through the CLI
+ enableregistration = false;
+ # Ues the host's timezone
+ timezone = config.time.timeZone;
+ # Use UNIX socket for serving the API
+ unixsocket = socketPath;
+ unixsocketmode = "0o660";
+ };
+
+ mailer = {
+ enabled = cfg.mail.enable;
+ };
+ };
+
+ environmentFiles = lib.optional cfg.mail.enable cfg.mail.configFile;
+ };
+
+ # This is a weird setup
+ my.services.nginx.virtualHosts = [
+ {
+ inherit subdomain;
+ # Serve the root for the web-ui
+ root = config.services.vikunja.package-frontend;
+
+ extraConfig = {
+ locations = {
+ "/" = {
+ tryFiles = "try_files $uri $uri/ /";
+ };
+
+ # Serve the API through a UNIX socket
+ "~* ^/(api|dav|\\.well-known)/" = {
+ proxyPass = "http://unix:${socketPath}";
+ extraConfig = ''
+ client_max_body_size 20M;
+ '';
+ };
+ };
+ };
+ }
+ ];
+
+ systemd.services.vikunja-api = {
+ serviceConfig = {
+ # Use a system user to simplify using the CLI
+ DynamicUser = lib.mkForce false;
+ # Set the user for postgres authentication
+ User = "vikunja";
+ # Create /run/vikunja/ to serve the UNIX socket
+ RuntimeDirectory = "vikunja";
+ };
+ };
+
+ users.users.vikunja = {
+ description = "Vikunja Service";
+ group = "vikunja";
+ isSystemUser = true;
+ };
+ users.groups.vikunja = { };
+
+ # Allow nginx to access the UNIX socket
+ users.users.nginx.extraGroups = [ "vikunja" ];
+
+ services.postgresql = {
+ ensureDatabases = [ "vikunja" ];
+ ensureUsers = [
+ {
+ name = "vikunja";
+ ensurePermissions = { "DATABASE vikunja" = "ALL PRIVILEGES"; };
+ }
+ ];
+ };
+
+ my.services.backup = {
+ paths = [
+ config.services.vikunja.settings.files.basepath
+ ];
+ };
+ };
+}
diff --git a/services/wireguard.nix b/modules/services/wireguard/default.nix
similarity index 91%
rename from services/wireguard.nix
rename to modules/services/wireguard/default.nix
index fc948f6..fc5518d 100644
--- a/services/wireguard.nix
+++ b/modules/services/wireguard/default.nix
@@ -5,9 +5,34 @@
{ config, lib, pkgs, ... }:
let
cfg = config.my.services.wireguard;
+ secrets = config.age.secrets;
hostName = config.networking.hostName;
- peers = config.my.secrets.wireguard.peers;
+ peers = {
+ # "Server"
+ porthos = {
+ clientNum = 1;
+ publicKey = "PLdgsizztddri0LYtjuNHr5r2E8D+yI+gM8cm5WDfHQ=";
+ externalIp = "91.121.177.163";
+ };
+
+ # "Clients"
+ aramis = {
+ clientNum = 2;
+ publicKey = "QJSWIBS1mXTpxYybLlKu/Y5wy0GFbUfn4yPzpF1DZDc=";
+ };
+
+ richelieu = {
+ clientNum = 3;
+ publicKey = "w4IADAj2Tt7Qe95a0RxDv9ovg/Dr/f3q1LrVOPF48Rk=";
+ };
+
+ # Sarah's iPhone
+ milady = {
+ clientNum = 4;
+ publicKey = "3MKEu4F6o8kww54xeAao5Uet86fv8z/QsZ2L2mOzqDQ=";
+ };
+ };
thisPeer = peers."${hostName}";
thisPeerIsServer = thisPeer ? externalIp;
# Only connect to clients from server, and only connect to server from clients
@@ -18,7 +43,7 @@ let
in
lib.filterAttrs shouldConnectToPeer allOthers;
- extIface = config.my.networking.externalInterface;
+ extIface = config.my.hardware.networking.externalInterface;
mkInterface = clientAllowedIPs: {
listenPort = cfg.port;
@@ -26,8 +51,7 @@ let
"${v4.subnet}.${toString thisPeer.clientNum}/${toString v4.mask}"
"${v6.subnet}::${toString thisPeer.clientNum}/${toHexString v6.mask}"
];
- # Insecure, I don't care
- privateKey = thisPeer.privateKey;
+ privateKeyFile = secrets."wireguard/private-key".path;
peers =
let
diff --git a/modules/services/wireguard/keys/milady/private-key.age b/modules/services/wireguard/keys/milady/private-key.age
new file mode 100644
index 0000000..fb84f91
--- /dev/null
+++ b/modules/services/wireguard/keys/milady/private-key.age
@@ -0,0 +1,9 @@
+age-encryption.org/v1
+-> ssh-ed25519 cKojmg gWB20jfimPCJHYjqxBSHYkL9Z/kGZ23dRu4PHp7oJj8
+z3dBymvgrGNtIXe3yQAzpm36uExPmD7DKjU6mMNw99U
+-> ssh-ed25519 jPowng aeWv6an+PmWRuk2eHOQhF7jvmld1I5p2LbSmehjUBBw
+Rn+ApMvZlO0ji6TCakCUc+1jK762UxOqVanmCsjB+80
+-> jDh})['\-grease |Y6J(8{ +v.7nKx
+WID+ZDtsOlPI0AW8ROvXH1s
+--- ZlSk2uv95UoKi5D94+tiQdZyxCVv6dlj6ajwYeDzmp0
+çön“¯`Wáø¸öm!Q3]ñËQ}}ý†ŽBy—€kÛuÐìçÝÆ€EÉ^…zO‡Ö[ÕV¨p šfâøÀ>¡Ä”ÌÌÖî
\ No newline at end of file
diff --git a/modules/services/wireguard/keys/richelieu/private-key.age b/modules/services/wireguard/keys/richelieu/private-key.age
new file mode 100644
index 0000000..e796688
--- /dev/null
+++ b/modules/services/wireguard/keys/richelieu/private-key.age
@@ -0,0 +1,10 @@
+age-encryption.org/v1
+-> ssh-ed25519 cKojmg rYhrpoTaFjLBGtbCXxEK7jZa+KnriEV/kWViIEjmuQs
+jHMSjxKIIqjUnpAcEo3JgsieI1iiA5/gKEx8+QFhDgY
+-> ssh-ed25519 jPowng 6sQQFvSbWdjgDYSKmJ/CBG+BTzxFghX4SaJ4GyACKWc
+OABJuh+Ta8q+G0onF/9bz3xxv4zTlHYlF4AjC5P6Y6I
+-> xwW|#D`-grease $xYH C m8lBk9
+OBqgvLNIurE0qNaSB7dO2/6dQkVXeLgf/3l9gGlRJ6ynhqwmbXOUa0vyj+OBz27O
+uI97+0y1TFAs3HN0Y8nj8LrwsafbDENu99JuVow2OuLKeSqc7sxOQQ
+--- 9filSHStPTJJGDLY7AWzIXu/6tK4X0okT522sc4OJTc
+M{²ûô˜¿$¹:NÙÐ[ݶ¬2xy8&äJ_{RÜLXü`W‡•€‡Í»xÑ*Pr`¾U²pøŽJÉ”øêF#åYÔý×òXæP”S s
\ No newline at end of file
diff --git a/modules/services/wireguard/keys/secrets.nix b/modules/services/wireguard/keys/secrets.nix
new file mode 100644
index 0000000..3985477
--- /dev/null
+++ b/modules/services/wireguard/keys/secrets.nix
@@ -0,0 +1,15 @@
+# Extra wireguard keys that are not hosts NixOS hosts
+let
+ keys = import ../../../../keys;
+
+ all = [
+ keys.users.ambroisie
+ ];
+in
+{
+ # Sarah's iPhone
+ "milady/private-key.age".publicKeys = all;
+
+ # My Android phone
+ "richelieu/private-key.age".publicKeys = all;
+}
diff --git a/modules/services/woodpecker/agent-docker/default.nix b/modules/services/woodpecker/agent-docker/default.nix
new file mode 100644
index 0000000..b18d075
--- /dev/null
+++ b/modules/services/woodpecker/agent-docker/default.nix
@@ -0,0 +1,45 @@
+{ config, lib, ... }:
+let
+ cfg = config.my.services.woodpecker;
+
+ hasRunner = (name: builtins.elem name cfg.runners);
+in
+{
+ config = lib.mkIf (cfg.enable && hasRunner "docker") {
+ services.woodpecker-agents = {
+ agents.docker = {
+ enable = true;
+
+ environment = {
+ WOODPECKER_SERVER = "localhost:${toString cfg.rpcPort}";
+ WOODPECKER_MAX_WORKFLOWS = "10";
+ WOODPECKER_BACKEND = "docker";
+ WOODPECKER_FILTER_LABELS = "type=docker";
+ WOODPECKER_HEALTHCHECK = "false";
+ };
+
+ environmentFile = [ cfg.sharedSecretFile ];
+
+ extraGroups = [ "docker" ];
+ };
+ };
+
+ # Make sure it is activated in that case
+ my.system.docker.enable = true;
+
+ # FIXME: figure out the issue
+ services.unbound.resolveLocalQueries = false;
+
+ # Adjust runner service for nix usage
+ systemd.services.woodpecker-agent-docker = {
+ after = [ "docker.socket" ]; # Needs the socket to be available
+ # might break deployment
+ restartIfChanged = false;
+ serviceConfig = {
+ BindPaths = [
+ "/var/run/docker.sock"
+ ];
+ };
+ };
+ };
+}
diff --git a/modules/services/woodpecker/agent-exec/default.nix b/modules/services/woodpecker/agent-exec/default.nix
new file mode 100644
index 0000000..ad30188
--- /dev/null
+++ b/modules/services/woodpecker/agent-exec/default.nix
@@ -0,0 +1,64 @@
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.my.services.woodpecker;
+
+ hasRunner = (name: builtins.elem name cfg.runners);
+in
+{
+ config = lib.mkIf (cfg.enable && hasRunner "exec") {
+ services.woodpecker-agents = {
+ agents.exec = {
+ enable = true;
+
+ environment = {
+ WOODPECKER_SERVER = "localhost:${toString cfg.rpcPort}";
+ WOODPECKER_MAX_WORKFLOWS = "10";
+ WOODPECKER_BACKEND = "local";
+ WOODPECKER_FILTER_LABELS = "type=exec";
+ WOODPECKER_HEALTHCHECK = "false";
+
+ NIX_REMOTE = "daemon";
+ PAGER = "cat";
+ };
+
+ environmentFile = [ cfg.sharedSecretFile ];
+ };
+ };
+
+ # Adjust runner service for nix usage
+ systemd.services.woodpecker-agent-exec = {
+ # Might break deployment
+ restartIfChanged = false;
+
+ path = with pkgs; [
+ woodpecker-plugin-git
+ bash
+ coreutils
+ git
+ git-lfs
+ gnutar
+ gzip
+ nix
+ ];
+
+ serviceConfig = {
+ # Same option as upstream, without @setuid
+ SystemCallFilter = lib.mkForce "~@clock @privileged @cpu-emulation @debug @keyring @module @mount @obsolete @raw-io @reboot @swap";
+
+ BindPaths = [
+ "/nix/var/nix/daemon-socket/socket"
+ "/run/nscd/socket"
+ ];
+ BindReadOnlyPaths = [
+ "/etc/passwd:/etc/passwd"
+ "/etc/group:/etc/group"
+ "/etc/nix:/etc/nix"
+ "${config.environment.etc."ssh/ssh_known_hosts".source}:/etc/ssh/ssh_known_hosts"
+ "/etc/machine-id"
+ # channels are dynamic paths in the nix store, therefore we need to bind mount the whole thing
+ "/nix/"
+ ];
+ };
+ };
+ };
+}
diff --git a/modules/services/woodpecker/default.nix b/modules/services/woodpecker/default.nix
new file mode 100644
index 0000000..34ffca6
--- /dev/null
+++ b/modules/services/woodpecker/default.nix
@@ -0,0 +1,46 @@
+{ lib, ... }:
+{
+ imports = [
+ ./agent-docker
+ ./agent-exec
+ ./server
+ ];
+
+ options.my.services.woodpecker = with lib; {
+ enable = mkEnableOption "Woodpecker CI";
+ runners = mkOption {
+ type = with types; listOf (enum [ "exec" "docker" ]);
+ default = [ ];
+ example = [ "exec" "docker" ];
+ description = "Types of runners to enable";
+ };
+ admin = mkOption {
+ type = types.str;
+ default = "ambroisie";
+ example = "admin";
+ description = "Name of the admin user";
+ };
+ port = mkOption {
+ type = types.port;
+ default = 3030;
+ example = 8080;
+ description = "Internal port of the Woodpecker UI";
+ };
+ rpcPort = mkOption {
+ type = types.port;
+ default = 3031;
+ example = 8080;
+ description = "Internal port of the Woodpecker UI";
+ };
+ secretFile = mkOption {
+ type = types.str;
+ example = "/run/secrets/woodpecker.env";
+ description = "Secrets to inject into Woodpecker server";
+ };
+ sharedSecretFile = mkOption {
+ type = types.str;
+ example = "/run/secrets/woodpecker.env";
+ description = "Shared RPC secret to inject into server and runners";
+ };
+ };
+}
diff --git a/modules/services/woodpecker/server/default.nix b/modules/services/woodpecker/server/default.nix
new file mode 100644
index 0000000..152e707
--- /dev/null
+++ b/modules/services/woodpecker/server/default.nix
@@ -0,0 +1,66 @@
+{ config, lib, ... }:
+let
+ cfg = config.my.services.woodpecker;
+in
+{
+ config = lib.mkIf cfg.enable {
+ services.woodpecker-server = {
+ enable = true;
+
+ environment = {
+ WOODPECKER_OPEN = "true";
+ WOODPECKER_HOST = "https://woodpecker.${config.networking.domain}";
+ WOODPECKER_DATABASE_DRIVER = "postgres";
+ WOODPECKER_DATABASE_DATASOURCE = "postgres:///woodpecker?host=/run/postgresql";
+ WOODPECKER_ADMIN = "${cfg.admin}";
+ WOODPECKER_SERVER_ADDR = ":${toString cfg.port}";
+ WOODPECKER_GRPC_ADDR = ":${toString cfg.rpcPort}";
+
+ WOODPECKER_GITEA = "true";
+ WOODPECKER_GITEA_URL = config.services.gitea.settings.server.ROOT_URL;
+
+ WOODPECKER_LOG_LEVEL = "debug";
+ };
+ };
+
+ systemd.services.woodpecker-server = {
+ serviceConfig = {
+ # Set username for DB access
+ User = "woodpecker";
+
+ BindPaths = [
+ # Allow access to DB path
+ "/run/postgresql"
+ ];
+
+ EnvironmentFile = [
+ cfg.secretFile
+ cfg.sharedSecretFile
+ ];
+ };
+ };
+
+ services.postgresql = {
+ enable = true;
+ ensureDatabases = [ "woodpecker" ];
+ ensureUsers = [{
+ name = "woodpecker";
+ ensurePermissions = {
+ "DATABASE woodpecker" = "ALL PRIVILEGES";
+ };
+ }];
+ };
+
+ my.services.nginx.virtualHosts = [
+ {
+ subdomain = "woodpecker";
+ inherit (cfg) port;
+ }
+ # I might want to be able to RPC from other hosts in the future
+ {
+ subdomain = "woodpecker-rpc";
+ port = cfg.rpcPort;
+ }
+ ];
+ };
+}
diff --git a/modules/system/boot/default.nix b/modules/system/boot/default.nix
new file mode 100644
index 0000000..3d8495e
--- /dev/null
+++ b/modules/system/boot/default.nix
@@ -0,0 +1,23 @@
+{ config, lib, ... }:
+let
+ cfg = config.my.system.boot;
+in
+{
+ options.my.system.boot = with lib; {
+ tmp = {
+ clean = mkEnableOption "clean `/tmp` on boot.";
+
+ tmpfs = my.mkDisableOption "mount `/tmp` as a tmpfs on boot.";
+ };
+ };
+
+ config = {
+ boot = {
+ tmp = {
+ cleanOnBoot = cfg.tmp.clean;
+
+ useTmpfs = cfg.tmp.tmpfs;
+ };
+ };
+ };
+}
diff --git a/modules/system/default.nix b/modules/system/default.nix
new file mode 100644
index 0000000..9fe3b57
--- /dev/null
+++ b/modules/system/default.nix
@@ -0,0 +1,15 @@
+# System-related modules
+{ ... }:
+
+{
+ imports = [
+ ./boot
+ ./docker
+ ./documentation
+ ./language
+ ./nix
+ ./packages
+ ./podman
+ ./users
+ ];
+}
diff --git a/modules/system/docker/default.nix b/modules/system/docker/default.nix
new file mode 100644
index 0000000..f051814
--- /dev/null
+++ b/modules/system/docker/default.nix
@@ -0,0 +1,27 @@
+# Podman related settings
+{ config, lib, ... }:
+let
+ cfg = config.my.system.docker;
+in
+{
+ options.my.system.docker = with lib; {
+ enable = mkEnableOption "docker configuration";
+ };
+
+ config = lib.mkIf cfg.enable {
+ virtualisation.docker = {
+ enable = true;
+
+ # Remove unused data on a weekly basis
+ autoPrune = {
+ enable = true;
+
+ dates = "weekly";
+
+ flags = [
+ "--all"
+ ];
+ };
+ };
+ };
+}
diff --git a/modules/system/documentation/default.nix b/modules/system/documentation/default.nix
new file mode 100644
index 0000000..304c811
--- /dev/null
+++ b/modules/system/documentation/default.nix
@@ -0,0 +1,43 @@
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.my.system.documentation;
+in
+{
+ options.my.system.documentation = with lib; {
+ enable = my.mkDisableOption "Documentation integration";
+
+ dev.enable = my.mkDisableOption "Documentation aimed at developers";
+
+ info.enable = my.mkDisableOption "Documentation aimed at developers";
+
+ man = {
+ enable = my.mkDisableOption "Documentation aimed at developers";
+
+ linux = my.mkDisableOption "Linux man pages (section 2 & 3)";
+ };
+
+ nixos.enable = my.mkDisableOption "NixOS documentation";
+ };
+
+ config = lib.mkIf cfg.enable {
+ documentation = {
+ enable = true;
+
+ dev.enable = cfg.dev.enable;
+
+ info.enable = cfg.info.enable;
+
+ man = {
+ enable = cfg.man.enable;
+ generateCaches = true;
+ };
+
+ nixos.enable = cfg.nixos.enable;
+ };
+
+ environment.systemPackages = with pkgs; lib.optionals cfg.man.linux [
+ man-pages
+ man-pages-posix
+ ];
+ };
+}
diff --git a/modules/system/language/default.nix b/modules/system/language/default.nix
new file mode 100644
index 0000000..f2bbcde
--- /dev/null
+++ b/modules/system/language/default.nix
@@ -0,0 +1,22 @@
+# Language settings
+{ config, lib, ... }:
+let
+ cfg = config.my.system.language;
+in
+{
+ options.my.system.language = with lib; {
+ enable = my.mkDisableOption "language configuration";
+
+ locale = mkOption {
+ type = types.str;
+ default = "en_US.UTF-8";
+ example = "fr_FR.UTF-8";
+ description = "Which locale to use for the system";
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ # Select internationalisation properties.
+ i18n.defaultLocale = cfg.locale;
+ };
+}
diff --git a/modules/system/nix/default.nix b/modules/system/nix/default.nix
new file mode 100644
index 0000000..47d6499
--- /dev/null
+++ b/modules/system/nix/default.nix
@@ -0,0 +1,107 @@
+# Nix related settings
+{ config, inputs, lib, options, pkgs, ... }:
+let
+ cfg = config.my.system.nix;
+
+ channels = lib.my.merge [
+ {
+ # Allow me to use my custom package using `nix run self#pkg`
+ self = inputs.self;
+ # Add NUR to run some packages that are only present there
+ nur = inputs.nur;
+ # Use pinned nixpkgs when using `nix run pkgs#`
+ pkgs = inputs.nixpkgs;
+ }
+ (lib.optionalAttrs cfg.inputs.overrideNixpkgs {
+ # ... And with `nix run nixpkgs#`
+ nixpkgs = inputs.nixpkgs;
+ })
+ ];
+in
+{
+ options.my.system.nix = with lib; {
+ enable = my.mkDisableOption "nix configuration";
+
+ cache = {
+ selfHosted = my.mkDisableOption "self-hosted cache";
+ };
+
+ inputs = {
+ link = my.mkDisableOption "link inputs to `/etc/nix/inputs/`";
+
+ addToRegistry = my.mkDisableOption "add inputs and self to registry";
+
+ addToNixPath = my.mkDisableOption "add inputs and self to nix path";
+
+ overrideNixpkgs = my.mkDisableOption "point nixpkgs to pinned system version";
+ };
+ };
+
+ config = lib.mkIf cfg.enable (lib.mkMerge [
+ {
+ assertions = [
+ {
+ assertion = cfg.inputs.addToNixPath -> cfg.inputs.link;
+ message = ''
+ enabling `my.system.nix.inputs.addToNixPath` needs to have
+ `my.system.nix.inputs.link = true`
+ '';
+ }
+ ];
+ }
+
+ {
+ nix = {
+ package = pkgs.nix;
+
+ settings = {
+ experimental-features = [ "nix-command" "flakes" ];
+ };
+ };
+ }
+
+ (lib.mkIf cfg.cache.selfHosted {
+ nix = {
+ settings = {
+ # The NixOS module adds the official Hydra cache by default
+ # No need to use `extra-*` options.
+ substituters = [
+ "https://cache.belanyi.fr/"
+ ];
+
+ trusted-public-keys = [
+ "cache.belanyi.fr:LPhrTqufwfxTceg1nRWueDWf7/2zSVY9K00pq2UI7tw="
+ ];
+ };
+ };
+ })
+
+ (lib.mkIf cfg.inputs.addToRegistry {
+ nix.registry =
+ let
+ makeEntry = v: { flake = v; };
+ makeEntries = lib.mapAttrs (lib.const makeEntry);
+ in
+ makeEntries channels;
+ })
+
+ (lib.mkIf cfg.inputs.link {
+ environment.etc =
+ let
+ makeLink = n: v: {
+ name = "nix/inputs/${n}";
+ value = { source = v.outPath; };
+ };
+ makeLinks = lib.mapAttrs' makeLink;
+ in
+ makeLinks channels;
+ })
+
+ (lib.mkIf cfg.inputs.addToNixPath {
+ nix.nixPath = [
+ "/etc/nix/inputs"
+ ]
+ ++ options.nix.nixPath.default;
+ })
+ ]);
+}
diff --git a/modules/system/packages/default.nix b/modules/system/packages/default.nix
new file mode 100644
index 0000000..5c29aa0
--- /dev/null
+++ b/modules/system/packages/default.nix
@@ -0,0 +1,35 @@
+# Common packages
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.my.system.packages;
+in
+{
+ options.my.system.packages = with lib; {
+ enable = my.mkDisableOption "packages configuration";
+
+ allowAliases = mkEnableOption "allow package aliases";
+
+ allowUnfree = my.mkDisableOption "allow unfree packages";
+ };
+
+ config = lib.mkIf cfg.enable {
+ environment.systemPackages = with pkgs; [
+ vim
+ wget
+ ];
+
+ programs = {
+ vim.defaultEditor = true; # Modal editing is life
+
+ zsh = {
+ enable = true; # Use integrations
+ # Disable global compinit when a user config exists
+ enableGlobalCompInit = !config.my.home.zsh.enable;
+ };
+ };
+
+ nixpkgs.config = {
+ inherit (cfg) allowAliases allowUnfree;
+ };
+ };
+}
diff --git a/modules/system/podman/default.nix b/modules/system/podman/default.nix
new file mode 100644
index 0000000..52630c7
--- /dev/null
+++ b/modules/system/podman/default.nix
@@ -0,0 +1,48 @@
+# Podman related settings
+{ config, lib, ... }:
+let
+ cfg = config.my.system.podman;
+in
+{
+ options.my.system.podman = with lib; {
+ enable = mkEnableOption "podman configuration";
+ };
+
+ config = lib.mkIf cfg.enable {
+ assertions = [
+ {
+ assertion = cfg.enable -> !config.my.system.docker.enable;
+ message = ''
+ `config.my.system.podman` is incompatible with
+ `config.my.system.docker`.
+ '';
+ }
+ ];
+
+ virtualisation.podman = {
+ enable = true;
+
+ # Use fake `docker` command to redirect to `podman`
+ dockerCompat = true;
+
+ # Expose a docker-like socket
+ dockerSocket.enable = true;
+
+ # Allow DNS resolution in the default network
+ defaultNetwork.settings = {
+ dns_enabled = true;
+ };
+
+ # Remove unused data on a weekly basis
+ autoPrune = {
+ enable = true;
+
+ dates = "weekly";
+
+ flags = [
+ "--all"
+ ];
+ };
+ };
+ };
+}
diff --git a/modules/system/users/default.nix b/modules/system/users/default.nix
new file mode 100644
index 0000000..27557bd
--- /dev/null
+++ b/modules/system/users/default.nix
@@ -0,0 +1,51 @@
+# User setup
+{ config, lib, pkgs, ... }:
+let
+ secrets = config.age.secrets;
+ cfg = config.my.system.users;
+ groupExists = grp: builtins.hasAttr grp config.users.groups;
+ groupsIfExist = builtins.filter groupExists;
+in
+{
+ options.my.system.users = with lib; {
+ enable = my.mkDisableOption "user configuration";
+ };
+
+ config = lib.mkIf cfg.enable {
+ users = {
+ mutableUsers = false; # I want it to be declarative.
+
+ users = {
+ root = {
+ passwordFile = secrets."users/root/hashed-password".path;
+ };
+
+ ${config.my.user.name} = {
+ passwordFile = secrets."users/ambroisie/hashed-password".path;
+ description = "Bruno BELANYI";
+ isNormalUser = true;
+ shell = pkgs.zsh;
+ extraGroups = groupsIfExist [
+ "audio" # sound control
+ "docker" # usage of `docker` socket
+ "media" # access to media files
+ "networkmanager" # wireless configuration
+ "plugdev" # usage of ZSA keyboard tools
+ "podman" # usage of `podman` socket
+ "video" # screen control
+ "wheel" # `sudo` for the user.
+ ];
+ openssh.authorizedKeys.keys = with builtins;
+ let
+ keyDir = ./ssh;
+ contents = readDir keyDir;
+ names = attrNames contents;
+ files = filter (name: contents.${name} == "regular") names;
+ keys = map (basename: readFile (keyDir + "/${basename}")) files;
+ in
+ keys;
+ };
+ };
+ };
+ };
+}
diff --git a/modules/ssh/aramis.pub b/modules/system/users/ssh/aramis.pub
similarity index 100%
rename from modules/ssh/aramis.pub
rename to modules/system/users/ssh/aramis.pub
diff --git a/modules/ssh/shared.pub b/modules/system/users/ssh/shared.pub
similarity index 100%
rename from modules/ssh/shared.pub
rename to modules/system/users/ssh/shared.pub
diff --git a/modules/users.nix b/modules/users.nix
deleted file mode 100644
index 1ace265..0000000
--- a/modules/users.nix
+++ /dev/null
@@ -1,35 +0,0 @@
-# User setup
-{ config, lib, pkgs, ... }:
-let
- my = config.my;
- groupIfExists = grp:
- lib.lists.optional
- (builtins.hasAttr grp config.users.groups)
- grp;
- groupsIfExist = builtins.concatMap groupIfExists;
-in
-{
- users.mutableUsers = false; # I want it to be declarative.
-
- # Define user accounts and passwords.
- users.users.root.hashedPassword = my.secrets.users.root.hashedPassword;
- users.users.ambroisie = {
- hashedPassword = my.secrets.users.ambroisie.hashedPassword;
- description = "Bruno BELANYI";
- isNormalUser = true;
- shell = pkgs.zsh;
- extraGroups = groupsIfExist [
- "media" # access to media files
- "plugdev" # usage of ZSA keyboard tools
- "wheel" # `sudo` for the user.
- ];
- openssh.authorizedKeys.keys = with builtins; let
- keyDir = ./ssh;
- contents = readDir keyDir;
- names = attrNames contents;
- files = filter (name: contents.${name} == "regular") names;
- keys = map (basename: readFile (keyDir + "/${basename}")) files;
- in
- keys;
- };
-}
diff --git a/overlays/default.nix b/overlays/default.nix
new file mode 100644
index 0000000..683e021
--- /dev/null
+++ b/overlays/default.nix
@@ -0,0 +1,6 @@
+# Automatically import all overlays in the directory
+let
+ files = builtins.readDir ./.;
+ overlays = builtins.removeAttrs files [ "default.nix" ];
+in
+builtins.mapAttrs (name: _: import "${./.}/${name}") overlays
diff --git a/pkgs/bw-pass/bw-pass b/pkgs/bw-pass/bw-pass
new file mode 100755
index 0000000..124714a
--- /dev/null
+++ b/pkgs/bw-pass/bw-pass
@@ -0,0 +1,75 @@
+#!/usr/bin/env bash
+
+AUTO_LOCK=900 # 15min timeout by default
+
+usage() {
+ printf '%s\n' "Usage: bw-pass [directory name] " >&2
+}
+
+prompt_pass() {
+ rofi -dmenu -password -no-fixed-num-lines -p "$1"
+}
+
+error_out() {
+ printf '%s\n' "$1" >&2
+ rofi -dmenu -no-fixed-num-lines -p "$1"
+ exit 1
+}
+
+login() {
+ local PASSWORD
+ PASSWORD="$(prompt_pass "Bitwarden Password")" || error_out "Cannot prompt password"
+ export BW_SESSION
+ BW_SESSION="$(bw unlock "$PASSWORD" --raw)" || error_out "Cannot unlock"
+}
+
+ensure_logged_in() {
+ # Use the same keyring as bitwarden-rofi for this
+
+ local KEY_ID
+ keyctl link @u @s
+ if ! KEY_ID="$(keyctl request user bw_session 2>/dev/null)"; then
+ login
+ KEY_ID="$(keyctl add user bw_session "$BW_SESSION" @u)"
+ fi
+
+ if [ "$AUTO_LOCK" -gt 0 ]; then
+ keyctl timeout "$KEY_ID" "$AUTO_LOCK"
+ fi
+ export BW_SESSION
+ BW_SESSION="$(keyctl pipe "$KEY_ID")"
+ keyctl unlink @u @s
+}
+
+query_password() {
+ # Either use with `query_password
+ # Or `query_password ` when the account has no directory
+
+ local FOLDER_ID
+ local PASSWORD
+
+ if [ $# -eq 2 ]; then
+ FOLDER_ID="$(bw list folders |
+ jq '.[] | select(.name == "'"$1"'") | .id' |
+ cut -d'"' -f2)"
+ shift
+ else
+ FOLDER_ID=null
+ fi
+ PASSWORD="$(bw list items --folderid "$FOLDER_ID" |
+ jq '.[] | select(.name == "'"$1"'") | .login.password' |
+ cut -d'"' -f2)"
+
+ if [ -z "$PASSWORD" ]; then
+ error_out "Did not find password for '$1'"
+ fi
+ printf '%s\n' "$PASSWORD"
+}
+
+if [ $# -lt 1 ] || [ $# -gt 2 ]; then
+ usage
+ exit 1
+fi
+
+ensure_logged_in
+query_password "$@"
diff --git a/pkgs/bw-pass/default.nix b/pkgs/bw-pass/default.nix
new file mode 100644
index 0000000..9b959f5
--- /dev/null
+++ b/pkgs/bw-pass/default.nix
@@ -0,0 +1,43 @@
+{ lib, bitwarden-cli, coreutils, jq, keyutils, makeWrapper, rofi, stdenvNoCC }:
+stdenvNoCC.mkDerivation rec {
+ pname = "bw-pass";
+ version = "0.1.0";
+
+ src = ./bw-pass;
+
+ nativeBuildInputs = [
+ makeWrapper
+ ];
+
+ dontUnpack = true;
+
+ dontBuild = true;
+
+ installPhase = ''
+ mkdir -p $out/bin
+ cp $src $out/bin/${pname}
+ chmod a+x $out/bin/${pname}
+ '';
+
+ wrapperPath = lib.makeBinPath [
+ bitwarden-cli
+ coreutils
+ jq
+ keyutils
+ rofi
+ ];
+
+ fixupPhase = ''
+ patchShebangs $out/bin/${pname}
+ wrapProgram $out/bin/${pname} --prefix PATH : "${wrapperPath}"
+ '';
+
+ meta = with lib; {
+ description = "A simple script to query a password from bitwarden";
+ homepage = "https://git.belanyi.fr/ambroisie/nix-config";
+ license = with licenses; [ mit ];
+ mainProgram = "bw-pass";
+ maintainers = with maintainers; [ ambroisie ];
+ platforms = platforms.linux;
+ };
+}
diff --git a/pkgs/change-audio/change-audio b/pkgs/change-audio/change-audio
new file mode 100755
index 0000000..612fecf
--- /dev/null
+++ b/pkgs/change-audio/change-audio
@@ -0,0 +1,75 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+NOTIFY=(
+ notify-send
+ -u low
+ -h string:x-canonical-private-synchronous:change-audio
+)
+
+do_change_volume() {
+ local args=()
+
+ if [ "$1" = "up" ]; then
+ args+=("-i")
+ else
+ args+=("-d")
+ fi
+ shift
+
+ # Do not boost over 100% unless explitily asked for
+ if [ "$1" = "--force" ] || [ "$1" = "-f" ]; then
+ args=("--allow-boost" "${args[@]}")
+ shift
+ fi
+
+ # Volume
+ args+=("$1")
+
+ pamixer "${args[@]}"
+
+ newVolume="$(pamixer --get-volume || true)"
+ [ "$(pamixer --get-volume-human)" = "muted" ] && isMuted=true
+
+ MSG="Set volume to $newVolume%"
+ if [ "${isMuted:-false}" = true ]; then
+ MSG="$MSG (muted)"
+ fi
+ "${NOTIFY[@]}" \
+ -h "int:value:$newVolume" \
+ -- "$MSG"
+}
+
+do_toggle() {
+ local args=()
+ if [ "${2:-audio}" = mic ]; then
+ args+=(--default-source)
+ MSG="Toggled microphone"
+ else
+ MSG="Toggled audio output"
+ fi
+
+ pamixer "${args[@]}" --toggle-mute
+
+ if [ "$(pamixer "${args[@]}" --get-mute)" = true ]; then
+ MSG="$MSG (muted)"
+ else
+ MSG="$MSG (unmuted)"
+ fi
+
+ "${NOTIFY[@]}" -- "$MSG"
+}
+
+case "$1" in
+ up|down)
+ do_change_volume "$@"
+ ;;
+ toggle)
+ do_toggle "$@"
+ ;;
+ *)
+ echo "No suche option '$1'" >&2
+ exit 1
+ ;;
+esac
diff --git a/pkgs/change-audio/default.nix b/pkgs/change-audio/default.nix
new file mode 100644
index 0000000..12814e1
--- /dev/null
+++ b/pkgs/change-audio/default.nix
@@ -0,0 +1,42 @@
+{ lib, libnotify, makeWrapper, pamixer, stdenvNoCC }:
+stdenvNoCC.mkDerivation rec {
+ pname = "change-audio";
+ version = "0.3.0";
+
+ src = ./change-audio;
+
+ nativeBuildInputs = [
+ makeWrapper
+ ];
+
+ dontUnpack = true;
+
+ dontBuild = true;
+
+ installPhase = ''
+ mkdir -p $out/bin
+ cp $src $out/bin/change-audio
+ chmod a+x $out/bin/change-audio
+ '';
+
+ wrapperPath = lib.makeBinPath [
+ libnotify
+ pamixer
+ ];
+
+ fixupPhase = ''
+ patchShebangs $out/bin/change-audio
+ wrapProgram $out/bin/change-audio --prefix PATH : "${wrapperPath}"
+ '';
+
+ meta = with lib; {
+ description = ''
+ A script to change the volume and notify about it
+ '';
+ homepage = "https://git.belanyi.fr/ambroisie/nix-config";
+ license = with licenses; [ mit ];
+ mainProgram = "change-audio";
+ maintainers = with maintainers; [ ambroisie ];
+ platforms = platforms.linux;
+ };
+}
diff --git a/pkgs/change-backlight/change-backlight b/pkgs/change-backlight/change-backlight
new file mode 100755
index 0000000..e178151
--- /dev/null
+++ b/pkgs/change-backlight/change-backlight
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+if [ "$1" = "up" ]; then
+ upDown="+$2%"
+else
+ upDown="$2%-"
+fi
+
+newBrightness="$(brightnessctl -m set "$upDown" | cut -d, -f4)"
+notify-send -u low \
+ -h string:x-canonical-private-synchronous:change-backlight \
+ -h "int:value:${newBrightness/\%/}" \
+ -- "Set brightness to $newBrightness"
diff --git a/pkgs/change-backlight/default.nix b/pkgs/change-backlight/default.nix
new file mode 100644
index 0000000..1f8c88c
--- /dev/null
+++ b/pkgs/change-backlight/default.nix
@@ -0,0 +1,42 @@
+{ lib, brightnessctl, libnotify, makeWrapper, stdenvNoCC }:
+stdenvNoCC.mkDerivation rec {
+ pname = "change-backlight";
+ version = "0.1.0";
+
+ src = ./change-backlight;
+
+ nativeBuildInputs = [
+ makeWrapper
+ ];
+
+ dontUnpack = true;
+
+ dontBuild = true;
+
+ installPhase = ''
+ mkdir -p $out/bin
+ cp $src $out/bin/change-backlight
+ chmod a+x $out/bin/change-backlight
+ '';
+
+ wrapperPath = lib.makeBinPath [
+ brightnessctl
+ libnotify
+ ];
+
+ fixupPhase = ''
+ patchShebangs $out/bin/change-backlight
+ wrapProgram $out/bin/change-backlight --prefix PATH : "${wrapperPath}"
+ '';
+
+ meta = with lib; {
+ description = ''
+ A script to change a screen's brightness and notify about it
+ '';
+ homepage = "https://git.belanyi.fr/ambroisie/nix-config";
+ license = with licenses; [ mit ];
+ mainProgram = "change-backlight";
+ maintainers = with maintainers; [ ambroisie ];
+ platforms = platforms.linux;
+ };
+}
diff --git a/pkgs/comma/comma b/pkgs/comma/comma
new file mode 100755
index 0000000..ba5c6ae
--- /dev/null
+++ b/pkgs/comma/comma
@@ -0,0 +1,33 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+print_err() {
+ printf "%s\n" "$1" >&2
+}
+
+usage() {
+ print_err "Usage: , [argument]..."
+}
+
+find_program() {
+ local CANDIDATE
+ CANDIDATE="$(nix-locate --top-level --minimal --at-root --whole-name "/bin/$1")"
+ if [ "$(printf '%s\n' "$CANDIDATE" | wc -l)" -gt 1 ]; then
+ CANDIDATE="$(printf '%s' "$CANDIDATE" | fzf-tmux)"
+ fi
+ printf '%s' "$CANDIDATE"
+}
+
+if [ $# -lt 1 ]; then
+ usage
+ exit 1
+fi
+
+PROGRAM="$(find_program "$1")"
+if [ -z "$PROGRAM" ]; then
+ print_err "No match found for $1"
+ exit 1
+fi
+
+nix shell "${COMMA_PKGS_FLAKE:-nixpkgs}#$PROGRAM" -c "$@"
diff --git a/pkgs/comma/default.nix b/pkgs/comma/default.nix
new file mode 100644
index 0000000..32e09d0
--- /dev/null
+++ b/pkgs/comma/default.nix
@@ -0,0 +1,40 @@
+{ lib, fzf, makeWrapper, nix-index, stdenvNoCC }:
+stdenvNoCC.mkDerivation rec {
+ pname = "comma";
+ version = "0.1.0";
+
+ src = ./comma;
+
+ nativeBuildInputs = [
+ makeWrapper
+ ];
+
+ dontUnpack = true;
+
+ dontBuild = true;
+
+ installPhase = ''
+ mkdir -p $out/bin
+ cp $src $out/bin/${meta.mainProgram}
+ chmod a+x $out/bin/${meta.mainProgram}
+ '';
+
+ wrapperPath = lib.makeBinPath [
+ fzf
+ nix-index
+ ];
+
+ fixupPhase = ''
+ patchShebangs $out/bin/${meta.mainProgram}
+ wrapProgram $out/bin/${meta.mainProgram} --prefix PATH : "${wrapperPath}"
+ '';
+
+ meta = with lib; {
+ description = "A simple script inspired by Shopify's comma, for modern Nix";
+ homepage = "https://git.belanyi.fr/ambroisie/nix-config";
+ license = with licenses; [ mit ];
+ mainProgram = ",";
+ maintainers = with maintainers; [ ambroisie ];
+ platforms = platforms.unix;
+ };
+}
diff --git a/pkgs/default.nix b/pkgs/default.nix
index 58f004a..c9e755a 100644
--- a/pkgs/default.nix
+++ b/pkgs/default.nix
@@ -1,18 +1,40 @@
{ pkgs }:
-rec {
+pkgs.lib.makeScope pkgs.newScope (pkgs: {
+ bw-pass = pkgs.callPackage ./bw-pass { };
+
+ change-audio = pkgs.callPackage ./change-audio { };
+
+ change-backlight = pkgs.callPackage ./change-backlight { };
+
+ comma = pkgs.callPackage ./comma { };
+
diff-flake = pkgs.callPackage ./diff-flake { };
- havm = pkgs.callPackage ./havm { };
+ dragger = pkgs.callPackage ./dragger { };
+
+ drone-rsync = pkgs.callPackage ./drone-rsync { };
+
+ drone-scp = pkgs.callPackage ./drone-scp { };
+
+ ff2mpv-go = pkgs.callPackage ./ff2mpv-go { };
+
+ i3-get-window-criteria = pkgs.callPackage ./i3-get-window-criteria { };
lohr = pkgs.callPackage ./lohr { };
- nolimips = pkgs.callPackage ./nolimips { };
+ matrix-notifier = pkgs.callPackage ./matrix-notifier { };
- podgrab = pkgs.callPackage ./podgrab { };
+ osc52 = pkgs.callPackage ./osc52 { };
- unbound-zones-adblock = pkgs.callPackage ./unbound-zones-adblock {
- inherit unified-hosts-lists;
- };
+ rbw-pass = pkgs.callPackage ./rbw-pass { };
+
+ unbound-zones-adblock = pkgs.callPackage ./unbound-zones-adblock { };
unified-hosts-lists = pkgs.callPackage ./unified-hosts-lists { };
-}
+
+ vimix-cursors = pkgs.callPackage ./vimix-cursors { };
+
+ volantes-cursors = pkgs.callPackage ./volantes-cursors { };
+
+ wifi-qr = pkgs.callPackage ./wifi-qr { };
+})
diff --git a/pkgs/diff-flake/default.nix b/pkgs/diff-flake/default.nix
index 9511952..9cccd20 100644
--- a/pkgs/diff-flake/default.nix
+++ b/pkgs/diff-flake/default.nix
@@ -1,20 +1,17 @@
-{ coreutils, git, gnused, makeWrapper, lib, shellcheck, stdenvNoCC }:
+{ lib, coreutils, git, gnused, makeWrapper, stdenvNoCC }:
stdenvNoCC.mkDerivation rec {
pname = "diff-flake";
- version = "0.1.0";
+ version = "0.4.0";
src = ./diff-flake;
- phases = [ "buildPhase" "installPhase" "fixupPhase" ];
-
- buildInputs = [
+ nativeBuildInputs = [
makeWrapper
- shellcheck
];
- buildPhase = ''
- shellcheck $src
- '';
+ dontUnpack = true;
+
+ dontBuild = true;
installPhase = ''
mkdir -p $out/bin
@@ -35,8 +32,10 @@ stdenvNoCC.mkDerivation rec {
meta = with lib; {
description = "Nix flake helper to visualize changes in closures";
- homepage = "https://gitea.belanyi.fr/ambroisie/nix-config";
+ homepage = "https://git.belanyi.fr/ambroisie/nix-config";
license = with licenses; [ mit ];
+ mainProgram = "diff-flake";
+ maintainers = with maintainers; [ ambroisie ];
platforms = platforms.unix;
};
}
diff --git a/pkgs/diff-flake/diff-flake b/pkgs/diff-flake/diff-flake
index ef03122..0572b4e 100755
--- a/pkgs/diff-flake/diff-flake
+++ b/pkgs/diff-flake/diff-flake
@@ -20,19 +20,21 @@ sanitize_output() {
fi
}
-add_shell() {
- local SYSTEM
- if [ $# -gt 0 ] && [ -n "$1" ]; then
- SYSTEM="$1"
- else
- SYSTEM="$(nix eval --raw --impure --expr 'builtins.currentSystem')"
- fi
- # Use 'inputDerivation' attribute to make sure that it is build-able
- FLAKE_OUTPUTS+=("devShell.$SYSTEM.inputDerivation")
+current_system() {
+ nix eval --raw --impure --expr 'builtins.currentSystem'
+}
+
+add_home() {
+ FLAKE_OUTPUTS+=("homeConfigurations.\"$1\".activationPackage")
}
add_host() {
- FLAKE_OUTPUTS+=("nixosConfigurations.$1.config.system.build.toplevel")
+ FLAKE_OUTPUTS+=("nixosConfigurations.\"$1\".config.system.build.toplevel")
+}
+
+add_shell() {
+ # Use 'inputDerivation' attribute to make sure that it is build-able
+ FLAKE_OUTPUTS+=("devShells.\"$(current_system)\".\"$1\".inputDerivation")
}
usage() {
@@ -52,17 +54,21 @@ usage() {
print_err " -p, --previous-rev"
print_err " which git revision should be considered the 'previous' state,"
print_err " defaults to HEAD~"
+ print_err " --home [name]"
+ print_err " specify the name of a home-manager output configuration whose"
+ print_err " closure should be diffed, can be used multiple times"
+ print_err " if no configuration name is given, defaults to current username"
print_err " --host [name]"
print_err " specify the name of a NixOS output configuration whose"
print_err " closure should be diffed, can be used multiple times"
print_err " if no host name is given, defaults to current hostname"
- print_err " --shell [system]"
- print_err " specify a specific system's devShell output whose closure"
+ print_err " --shell [name]"
+ print_err " specify a specific devShell configuration name whose closure"
print_err " should be diffed, can be used multiple times"
- print_err " if no system is given, defaults to current system"
+ print_err " if no name is given, defaults to 'default'"
print_err ""
print_err "when no flake outputs are specified, automatically queries for"
- print_err "all NixOS configurations, and devShell for current system"
+ print_err "all NixOS configurations, and devShells for current system"
}
is_option() {
@@ -95,6 +101,14 @@ parse_args() {
PREVIOUS_REV="$(git rev-parse "$1")"
shift
;;
+ --home)
+ if [ $# -gt 0 ] && ! is_option "$1"; then
+ add_home "$1"
+ shift
+ else
+ add_home "$USER"
+ fi
+ ;;
--host)
if [ $# -gt 0 ] && ! is_option "$1"; then
add_host "$1"
@@ -108,7 +122,7 @@ parse_args() {
add_shell "$1"
shift
else
- add_shell
+ add_shell "default"
fi
;;
--)
@@ -124,12 +138,24 @@ parse_args() {
done
}
+list_home_configurations() {
+ nix eval '.#homeConfigurations' \
+ --apply 'attrs: with builtins; concatStringsSep "\n" (attrNames attrs)' \
+ --raw
+}
+
list_nixos_configurations() {
nix eval '.#nixosConfigurations' \
--apply 'attrs: with builtins; concatStringsSep "\n" (attrNames attrs)' \
--raw
}
+list_dev_shells() {
+ nix eval ".#devShells.\"$(current_system)\"" \
+ --apply 'attrs: with builtins; concatStringsSep "\n" (attrNames attrs)' \
+ --raw
+}
+
diff_output() {
local PREV NEW;
PREV="$(mktemp --dry-run)"
@@ -149,10 +175,15 @@ diff_output() {
parse_args "$@"
if [ "${#FLAKE_OUTPUTS[@]}" -eq 0 ]; then
+ for home in $(list_home_configurations); do
+ add_home "$home"
+ done
for host in $(list_nixos_configurations); do
add_host "$host"
done
- add_shell
+ for shell in $(list_dev_shells); do
+ add_shell "$shell"
+ done
fi
for out in "${FLAKE_OUTPUTS[@]}"; do
diff --git a/pkgs/dragger/default.nix b/pkgs/dragger/default.nix
new file mode 100644
index 0000000..9eda7df
--- /dev/null
+++ b/pkgs/dragger/default.nix
@@ -0,0 +1,30 @@
+{ lib, fetchFromGitHub, qt5, }:
+qt5.mkDerivation rec {
+ pname = "dragger";
+ version = "0.1.0";
+
+ src = fetchFromGitHub {
+ owner = "ambroisie";
+ repo = "dragger";
+ rev = "v${version}";
+ hash = "sha256-WAC720DxfkQxy1BeeGzE6IerFb4ejoMRAPEJv5HGDHM=";
+ };
+
+ configurePhase = ''
+ qmake
+ '';
+
+ installPhase = ''
+ mkdir -p $out/bin
+ cp dragger $out/bin
+ '';
+
+ meta = with lib; {
+ description = "A CLI drag-and-drop tool";
+ homepage = "https://git.belanyi.fr/ambroisie/dragger";
+ license = licenses.mit;
+ mainProgram = "dragger";
+ maintainers = with maintainers; [ ambroisie ];
+ platforms = platforms.linux;
+ };
+}
diff --git a/pkgs/drone-rsync/default.nix b/pkgs/drone-rsync/default.nix
new file mode 100644
index 0000000..a7c2cbc
--- /dev/null
+++ b/pkgs/drone-rsync/default.nix
@@ -0,0 +1,41 @@
+{ lib, makeWrapper, openssh, rsync, sshpass, stdenvNoCC }:
+stdenvNoCC.mkDerivation rec {
+ pname = "drone-rsync";
+ version = "0.1.0";
+
+ src = ./drone-rsync;
+
+ nativeBuildInputs = [
+ makeWrapper
+ ];
+
+ dontUnpack = true;
+
+ dontBuild = true;
+
+ installPhase = ''
+ mkdir -p $out/bin
+ cp $src $out/bin/${pname}
+ chmod a+x $out/bin/${pname}
+ '';
+
+ wrapperPath = lib.makeBinPath [
+ openssh
+ rsync
+ sshpass
+ ];
+
+ fixupPhase = ''
+ patchShebangs $out/bin/${pname}
+ wrapProgram $out/bin/${pname} --prefix PATH : "${wrapperPath}"
+ '';
+
+ meta = with lib; {
+ description = "Helper script to run rsync in a CI pipeline";
+ homepage = "https://git.belanyi.fr/ambroisie/nix-config";
+ license = with licenses; [ mit ];
+ mainProgram = "drone-rsync";
+ maintainers = with maintainers; [ ambroisie ];
+ platforms = platforms.unix;
+ };
+}
diff --git a/pkgs/drone-rsync/drone-rsync b/pkgs/drone-rsync/drone-rsync
new file mode 100755
index 0000000..b6491e7
--- /dev/null
+++ b/pkgs/drone-rsync/drone-rsync
@@ -0,0 +1,31 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+ARGS=(
+ # Show readable progress in log
+ --verbose
+ --human-readable
+ --progress
+ # Have a one-to-one copy
+ --archive
+ --compress
+ --recursive
+ --delete
+ # Configure ssh client
+ --rsh "ssh -p ${SYNC_PORT:-22} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
+)
+
+eval "$(ssh-agent)"
+SSHPASS="${SYNC_PASSPHRASE:-}" sshpass -P 'passphrase' -v -e ssh-add <(echo "${SYNC_KEY}")
+
+if [ -n "${SYNC_DRY_RUN:-}" ]; then
+ ARGS+=(--dry-run)
+fi
+
+# shellcheck disable=2086
+# FIXME: have a safer way to allow globbing the source
+rsync \
+ "${ARGS[@]}" \
+ ${SYNC_SOURCE} \
+ "${SYNC_USERNAME}@${SYNC_HOST}:${SYNC_TARGET}"
diff --git a/pkgs/drone-scp/default.nix b/pkgs/drone-scp/default.nix
new file mode 100644
index 0000000..7437b06
--- /dev/null
+++ b/pkgs/drone-scp/default.nix
@@ -0,0 +1,25 @@
+{ lib, buildGoModule, fetchFromGitHub }:
+buildGoModule rec {
+ pname = "drone-scp";
+ version = "1.6.3";
+
+ src = fetchFromGitHub {
+ owner = "appleboy";
+ repo = "drone-scp";
+ rev = "v${version}";
+ hash = "sha256-ELjPqoRR4O6gmc/PgthQuSXuSTQNzBZoAUT80zVVbV0=";
+ };
+
+ vendorHash = "sha256-/c103hTJ/Qdz2KTkdl/ACvAaSSTKcl1DQY3+Us6OxaI=";
+
+ doCheck = false; # Needs a specific user...
+
+ meta = with lib; {
+ description = ''
+ Copy files and artifacts via SSH using a binary, docker or Drone CI
+ '';
+ homepage = "https://github.com/appleboy/drone-scp";
+ license = licenses.mit;
+ mainProgram = "drone-scp";
+ };
+}
diff --git a/pkgs/ff2mpv-go/default.nix b/pkgs/ff2mpv-go/default.nix
new file mode 100644
index 0000000..8c59bf5
--- /dev/null
+++ b/pkgs/ff2mpv-go/default.nix
@@ -0,0 +1,31 @@
+{ lib, buildGoModule, fetchgit, mpv }:
+buildGoModule rec {
+ pname = "ff2mpv-go";
+ version = "1.0.1";
+
+ src = fetchgit {
+ url = "https://git.clsr.net/util/ff2mpv-go/";
+ rev = "v${version}";
+ hash = "sha256-e/AuOA3isFTyBf97Zwtr16yo49UdYzvktV5PKB/eH/s=";
+ };
+
+ vendorHash = null;
+
+ postPatch = ''
+ sed -i -e 's,"mpv","${lib.getExe mpv}",' ff2mpv.go
+ '';
+
+ postInstall = ''
+ mkdir -p "$out/lib/mozilla/native-messaging-hosts"
+ $out/bin/ff2mpv-go --manifest > "$out/lib/mozilla/native-messaging-hosts/ff2mpv.json"
+ '';
+
+ meta = with lib; {
+ description = ''
+ Native messaging host for ff2mpv written in Go.
+ '';
+ homepage = "https://git.clsr.net/util/ff2mpv-go/";
+ license = licenses.publicDomain;
+ mainProgram = "ff2mpv-go";
+ };
+}
diff --git a/pkgs/havm/default.nix b/pkgs/havm/default.nix
deleted file mode 100644
index 20817fc..0000000
--- a/pkgs/havm/default.nix
+++ /dev/null
@@ -1,30 +0,0 @@
-{ fetchurl, ghc, lib, stdenv, which }:
-stdenv.mkDerivation rec {
- pname = "havm";
- version = "0.28";
-
- src = fetchurl {
- url = "https://www.lrde.epita.fr/~tiger/download/${pname}-${version}.tar.gz";
- sha256 = "sha256-FDi4FZ8rjGqRkFlROtcJsv+mks7MmIXQGV4bZrwkQrA=";
- };
-
- buildInputs = [
- ghc
- which # Used by tests
- ];
-
- doCheck = true;
-
- meta = with lib; {
- description = "A simple virtual machine to execute Andrew Appel's HIR/LIR";
- longDescription = ''
- HAVM is a virtual machine designed to execute simple register based high
- level intermediate code. It is based on the intermediate representations
- ("canonicalized" or not) defined by Andrew Appel in his "Modern Compiler
- Implementation".
- '';
- homepage = "https://www.lrde.epita.fr/wiki/Havm";
- license = licenses.gpl2Plus;
- platforms = platforms.all;
- };
-}
diff --git a/pkgs/i3-get-window-criteria/default.nix b/pkgs/i3-get-window-criteria/default.nix
new file mode 100644
index 0000000..2fc840d
--- /dev/null
+++ b/pkgs/i3-get-window-criteria/default.nix
@@ -0,0 +1,42 @@
+{ lib, coreutils, gnused, makeWrapper, stdenvNoCC, xorg }:
+stdenvNoCC.mkDerivation rec {
+ pname = "i3-get-window-criteria";
+ version = "0.1.0";
+
+ src = ./i3-get-window-criteria;
+
+ nativeBuildInputs = [
+ makeWrapper
+ ];
+
+ dontUnpack = true;
+
+ dontBuild = true;
+
+ installPhase = ''
+ mkdir -p $out/bin
+ cp $src $out/bin/${pname}
+ chmod a+x $out/bin/${pname}
+ '';
+
+ wrapperPath = lib.makeBinPath [
+ coreutils
+ gnused
+ xorg.xprop
+ xorg.xwininfo
+ ];
+
+ fixupPhase = ''
+ patchShebangs $out/bin/${pname}
+ wrapProgram $out/bin/${pname} --prefix PATH : "${wrapperPath}"
+ '';
+
+ meta = with lib; {
+ description = "Helper script to query i3 window criterions";
+ homepage = "https://git.belanyi.fr/ambroisie/nix-config";
+ license = with licenses; [ mit ];
+ mainProgram = "i3-get-window-criteria";
+ maintainers = with maintainers; [ ambroisie ];
+ platforms = platforms.unix;
+ };
+}
diff --git a/pkgs/i3-get-window-criteria/i3-get-window-criteria b/pkgs/i3-get-window-criteria/i3-get-window-criteria
new file mode 100755
index 0000000..dba386e
--- /dev/null
+++ b/pkgs/i3-get-window-criteria/i3-get-window-criteria
@@ -0,0 +1,30 @@
+#!/usr/bin/env bash
+
+# i3-get-window-criteria - Get criteria for use with i3 config commands
+
+# To use, run this script, then click on a window.
+# Output is in the format: [= = ...]
+
+# Known problem: when WM_NAME is used as fallback for the 'title=""' criterion,
+# quotes in "" are not escaped properly. This is a problem with the output of `xprop`,
+# reported upstream: https://bugs.freedesktop.org/show_bug.cgi?id=66807
+
+match_int='[0-9][0-9]*'
+match_string='".*"'
+match_qstring='"[^"\\]*(\\.[^"\\]*)*"' # NOTE: Adds 1 backreference
+
+{
+ # Run xwininfo, get window id
+ window_id=$(xwininfo -int | sed -nre "s/^xwininfo: Window id: ($match_int) .*$/\1/p")
+ echo "id=$window_id"
+
+ # Run xprop, transform its output into i3 criteria. Handle fallback to
+ # WM_NAME when _NET_WM_NAME isn't set
+ xprop -id "$window_id" |
+ sed -nr \
+ -e "s/^WM_CLASS\(STRING\) = ($match_qstring), ($match_qstring)$/instance=\1\nclass=\3/p" \
+ -e "s/^WM_WINDOW_ROLE\(STRING\) = ($match_qstring)$/window_role=\1/p" \
+ -e "/^WM_NAME\(STRING\) = ($match_string)$/ {s//title=\1/; h}" \
+ -e "/^_NET_WM_NAME\(UTF8_STRING\) = ($match_qstring)$/ {s//title=\1/; h}" \
+ -e '$ {g; p}'
+} | sort | tr "\n" " " | sed -r 's/^ *(.*) $/[\1]\n/'
diff --git a/pkgs/lohr/default.nix b/pkgs/lohr/default.nix
index 1ceb018..a83b092 100644
--- a/pkgs/lohr/default.nix
+++ b/pkgs/lohr/default.nix
@@ -1,21 +1,23 @@
-{ fetchFromGitHub, lib, rustPlatform }:
+{ lib, fetchFromGitHub, rustPlatform }:
rustPlatform.buildRustPackage rec {
pname = "lohr";
- version = "0.4.0";
+ version = "0.4.2";
src = fetchFromGitHub {
owner = "alarsyo";
repo = "lohr";
rev = "v${version}";
- sha256 = "sha256-MplTVJG+SoeLMXQP+ix/zM3OSHuQmZnunn900YnyCBw=";
+ hash = "sha256-2pN/Me5fCdE++TzBUswPXzjuUIIB7Uck+Scp361JgE4=";
};
- cargoSha256 = "sha256-iuMJj8tqetlmdfsrfudnU1afwUzjls/UdYLq1u0gr+g=";
+ cargoHash = "sha256-YHg4b6rKcnVJSDoWh9/o+p40NBog65Gd2/UwIDXiUe0=";
meta = with lib; {
description = "Git mirroring daemon";
homepage = "https://github.com/alarsyo/lohr";
license = with licenses; [ mit asl20 ];
+ mainProgram = "lohr";
+ maintainers = with maintainers; [ ambroisie ];
platforms = platforms.unix;
};
}
diff --git a/pkgs/matrix-notifier/default.nix b/pkgs/matrix-notifier/default.nix
new file mode 100644
index 0000000..a96cb61
--- /dev/null
+++ b/pkgs/matrix-notifier/default.nix
@@ -0,0 +1,45 @@
+{ lib, curl, jq, fetchFromGitHub, makeWrapper, pandoc, stdenvNoCC }:
+stdenvNoCC.mkDerivation rec {
+ pname = "matrix-notifier";
+ version = "0.3.0";
+
+ src = fetchFromGitHub {
+ owner = "ambroisie";
+ repo = "matrix-notifier";
+ rev = "v${version}";
+ hash = "sha256-NE9RO0ep2ibrT9EUPGTnUE3ofdNTCHwelxnX9tCflg0=";
+ };
+
+ nativeBuildInputs = [
+ makeWrapper
+ ];
+
+ installPhase = ''
+ mkdir -p $out/bin
+ cp $src/${pname} $out/bin/${pname}
+ chmod a+x $out/bin/${pname}
+ '';
+
+ wrapperPath = lib.makeBinPath [
+ curl
+ jq
+ pandoc
+ ];
+
+ fixupPhase = ''
+ patchShebangs $out/bin/${pname}
+ wrapProgram $out/bin/${pname} --prefix PATH : "${wrapperPath}"
+ '';
+
+ meta = with lib; {
+ description = ''
+ A very simple bash script that can be used to send a message to
+ a Matrix room
+ '';
+ homepage = "https://git.belanyi.fr/ambroisie/${pname}";
+ license = licenses.mit;
+ mainProgram = "matrix-notifier";
+ maintainers = with maintainers; [ ambroisie ];
+ platforms = platforms.unix;
+ };
+}
diff --git a/pkgs/nolimips/default.nix b/pkgs/nolimips/default.nix
deleted file mode 100644
index ff5c9b0..0000000
--- a/pkgs/nolimips/default.nix
+++ /dev/null
@@ -1,23 +0,0 @@
-{ fetchurl, gnulib, lib, stdenv }:
-stdenv.mkDerivation rec {
- pname = "nolimips";
- version = "0.11";
-
- src = fetchurl {
- url = "https://www.lrde.epita.fr/~tiger/download/${pname}-${version}.tar.gz";
- sha256 = "sha256-OjbfcBwCZtFP0usz8YXA0lN8xs0jS4I19mkh9p7VHc8=";
- };
-
- doCheck = true;
-
- meta = with lib; {
- description = "A basic MIPS architecture simulator";
- longDescription = ''
- A basic MIPS architecture simulator, which implements a few system calls
- and supports an arbitrary number of registers.
- '';
- homepage = "https://www.lrde.epita.fr/wiki/Nolimips";
- license = licenses.gpl2;
- platforms = platforms.all;
- };
-}
diff --git a/pkgs/osc52/default.nix b/pkgs/osc52/default.nix
new file mode 100644
index 0000000..d4b0c08
--- /dev/null
+++ b/pkgs/osc52/default.nix
@@ -0,0 +1,41 @@
+{ lib, coreutils, makeWrapper, stdenvNoCC }:
+stdenvNoCC.mkDerivation rec {
+ pname = "osc52";
+ version = "0.1.0";
+
+ src = ./osc52;
+
+ nativeBuildInputs = [
+ makeWrapper
+ ];
+
+ dontUnpack = true;
+
+ dontBuild = true;
+
+ installPhase = ''
+ mkdir -p $out/bin
+ cp $src $out/bin/osc52
+ chmod a+x $out/bin/osc52
+ '';
+
+ wrapperPath = lib.makeBinPath [
+ coreutils
+ ];
+
+ fixupPhase = ''
+ patchShebangs $out/bin/osc52
+ wrapProgram $out/bin/osc52 --prefix PATH : "${wrapperPath}"
+ '';
+
+ meta = with lib; {
+ description = ''
+ A script to copy strings using the OSC52 escape sequence
+ '';
+ homepage = "https://git.belanyi.fr/ambroisie/nix-config";
+ license = with licenses; [ mit ];
+ mainProgram = "osc52";
+ maintainers = with maintainers; [ ambroisie ];
+ platforms = platforms.linux;
+ };
+}
diff --git a/pkgs/osc52/osc52 b/pkgs/osc52/osc52
new file mode 100755
index 0000000..f64ccb6
--- /dev/null
+++ b/pkgs/osc52/osc52
@@ -0,0 +1,153 @@
+#!/usr/bin/env bash
+
+# Max length of the OSC 52 sequence.
+: "${OSC52_MAX_SEQUENCE:=100000}"
+# Whether to disable tmux/screen DCS escape sequences
+: "${OSC52_NO_DCS:=0}"
+
+die() {
+ echo "ERROR: $*"
+ exit 1
+}
+
+usage() {
+ if [ $# -gt 0 ]; then
+ exec 1>&2
+ fi
+
+ cat << EOF
+Usage: $0 [options] [string]
+Send an arbitrary string to the terminal clipboard using the OSC 52 escape
+sequence as specified in xterm:
+ https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands
+ Section "Operating System Commands", Ps => 52.
+The data can either be read from stdin:
+ $ echo "hello world" | $0
+Or specified on the command line:
+ $ $0 "hello world"
+Options:
+ -h, --help This screen.
+ -d, --no-dcs Disable tmux/screen specific DCS sequences, only use OSC 52
+ -f, --force Ignore max byte limit (${OSC52_MAX_SEQUENCE})
+ -- Stop options processing
+EOF
+
+ if [ $# -gt 0 ]; then
+ echo
+ die "$@"
+ else
+ exit 0
+ fi
+}
+
+tmux_seq() {
+ # shellcheck disable=1003
+ printf '\033Ptmux;\033%s\033\\' "$1"
+}
+
+screen_seq() {
+ # Screen limits the length of string sequences, so we have to break it up.
+ # Going by the screen history:
+ # (v4.2.1) Apr 2014 - today: 768 bytes
+ # Aug 2008 - Apr 2014 (v4.2.0): 512 bytes
+ # ??? - Aug 2008 (v4.0.3): 256 bytes
+ local limit=768
+ # We go 4 bytes under the limit because we're going to insert two bytes
+ # before (\eP) and 2 bytes after (\e\) each string.
+ printf '%s' "$1" |
+ sed -E "s:.{$((limit - 4))}:&\n:g" |
+ sed -E -e 's:^:\x1bP:' -e 's:$:\x1b\\:' |
+ tr -d '\n'
+}
+
+osc52_seq() {
+ printf '%s' "$1"
+}
+
+print_seq() {
+ local seq="$1"
+
+ if [ "${OSC52_NO_DCS}" != 0 ]; then
+ # Override TERM to avoid tmux/screen DCS escape logic
+ TERM=dummy
+ fi
+
+ case ${TERM-} in
+ screen*)
+ # Since tmux defaults to setting TERM=screen, special case it.
+ if [ -n "${TMUX-}" ]; then
+ tmux_seq "${seq}"
+ else
+ screen_seq "${seq}"
+ fi
+ ;;
+ tmux*)
+ tmux_seq "${seq}"
+ ;;
+ *)
+ osc52_seq "${seq}"
+ ;;
+ esac
+}
+
+b64enc() {
+ base64 | tr -d '\n'
+}
+
+copy() {
+ local str
+ if [ $# -eq 0 ]; then
+ str="$(b64enc)"
+ else
+ str="$(printf '%s' "$1" | b64enc)"
+ fi
+
+ if [ "${OSC52_MAX_SEQUENCE}" -gt 0 ]; then
+ local len=${#str}
+ if [ "${len}" -gt "${OSC52_MAX_SEQUENCE}" ]; then
+ die "selection too long to send to terminal:" \
+ "${OSC52_MAX_SEQUENCE} limit, ${len} attempted"
+ fi
+ fi
+
+ print_seq "$(printf '\033]52;c;%s\a' "${str}")"
+}
+
+main() {
+ set -e
+
+ local args=()
+ while [ $# -gt 0 ]; do
+ case $1 in
+ -h | --help)
+ usage
+ ;;
+ -f | --force)
+ OSC52_MAX_SEQUENCE=0
+ ;;
+ -d | --no-dcs)
+ OSC52_NO_DCS=1
+ ;;
+ --)
+ shift
+ args+=("$@")
+ break
+ ;;
+ -*)
+ usage "Unknown option: $1"
+ ;;
+ *)
+ args+=("$1")
+ ;;
+ esac
+ shift
+ done
+
+ if [ "${#args[@]}" -gt 1 ]; then
+ usage "Only supply one argument"
+ fi
+
+ copy "${args[@]}"
+}
+
+main "$@"
diff --git a/pkgs/podgrab/default.nix b/pkgs/podgrab/default.nix
deleted file mode 100644
index 7f18416..0000000
--- a/pkgs/podgrab/default.nix
+++ /dev/null
@@ -1,29 +0,0 @@
-{ buildGoModule, fetchFromGitHub, lib }:
-
-buildGoModule rec {
- pname = "podgrab";
- version = "2021-03-26";
-
- src = fetchFromGitHub {
- owner = "akhilrex";
- repo = "podgrab";
- rev = "3179a875b8b638fb86d0e829d12a9761c1cd7f90";
- sha256 = "sha256-vhxIm20ZUi+RusrAsSY54tv/D570/oMO5qLz9dNqgqo=";
- };
-
- vendorSha256 = "sha256-xY9xNuJhkWPgtqA/FBVIp7GuWOv+3nrz6l3vaZVLlIE=";
-
- postInstall = ''
- mkdir -p $out/share/
- cp -r "$src/client" "$out/share/"
- cp -r "$src/webassets" "$out/share/"
- '';
-
- meta = with lib; {
- description = ''
- A self-hosted podcast manager to download episodes as soon as they become live
- '';
- homepage = "https://github.com/akhilrex/podgrab";
- license = licenses.gpl3;
- };
-}
diff --git a/pkgs/rbw-pass/default.nix b/pkgs/rbw-pass/default.nix
new file mode 100644
index 0000000..6f9ff0a
--- /dev/null
+++ b/pkgs/rbw-pass/default.nix
@@ -0,0 +1,41 @@
+{ lib, coreutils, makeWrapper, rbw, rofi, stdenvNoCC }:
+stdenvNoCC.mkDerivation rec {
+ pname = "rbw-pass";
+ version = "0.1.0";
+
+ src = ./rbw-pass;
+
+ nativeBuildInputs = [
+ makeWrapper
+ ];
+
+ dontUnpack = true;
+
+ dontBuild = true;
+
+ installPhase = ''
+ mkdir -p $out/bin
+ cp $src $out/bin/${pname}
+ chmod a+x $out/bin/${pname}
+ '';
+
+ wrapperPath = lib.makeBinPath [
+ rbw
+ coreutils
+ rofi
+ ];
+
+ fixupPhase = ''
+ patchShebangs $out/bin/${pname}
+ wrapProgram $out/bin/${pname} --prefix PATH : "${wrapperPath}"
+ '';
+
+ meta = with lib; {
+ description = "A simple script to query a password from rbw";
+ homepage = "https://git.belanyi.fr/ambroisie/nix-config";
+ license = with licenses; [ mit ];
+ mainProgram = "rbw-pass";
+ maintainers = with maintainers; [ ambroisie ];
+ platforms = platforms.linux;
+ };
+}
diff --git a/pkgs/rbw-pass/rbw-pass b/pkgs/rbw-pass/rbw-pass
new file mode 100755
index 0000000..90e916c
--- /dev/null
+++ b/pkgs/rbw-pass/rbw-pass
@@ -0,0 +1,43 @@
+#!/usr/bin/env bash
+
+usage() {
+ printf '%s\n' "Usage: bw-pass [directory name] " >&2
+}
+
+error_out() {
+ printf '%s\n' "$1" >&2
+ rofi -dmenu -no-fixed-num-lines -p "$1"
+ exit 1
+}
+
+ensure_logged_in() {
+ rbw login
+}
+
+query_password() {
+ # Either use with `query_password
+ # Or `query_password ` when the account has no directory
+
+ local FOLDER_ARGS=()
+ local PASSWORD
+
+ # FIXME: no way to enforce filering by "no folder"
+ if [ $# -eq 2 ]; then
+ FOLDER_ARGS+=(--folder "$1")
+ shift
+ fi
+ PASSWORD="$(rbw get "${FOLDER_ARGS[@]}" "$1")"
+
+ if [ -z "$PASSWORD" ]; then
+ error_out "Did not find password for '$1'"
+ fi
+ printf '%s\n' "$PASSWORD"
+}
+
+if [ $# -lt 1 ] || [ $# -gt 2 ]; then
+ usage
+ exit 1
+fi
+
+ensure_logged_in
+query_password "$@"
diff --git a/pkgs/unbound-zones-adblock/default.nix b/pkgs/unbound-zones-adblock/default.nix
index c4309bd..b8392ae 100644
--- a/pkgs/unbound-zones-adblock/default.nix
+++ b/pkgs/unbound-zones-adblock/default.nix
@@ -1,16 +1,16 @@
-{ fetchFromGitHub, gawk, lib, stdenvNoCC, unified-hosts-lists }:
-stdenvNoCC.mkDerivation rec {
+{ lib, gawk, stdenvNoCC, unified-hosts-lists }:
+stdenvNoCC.mkDerivation {
name = "unbound-zones-adblock";
version = unified-hosts-lists.version;
src = unified-hosts-lists;
- phases = [ "installPhase" ];
+ dontUnpack = true;
installPhase =
let
gawkCmd = lib.concatStringsSep " " [
- ''${gawk}/bin/awk''
+ (lib.getExe gawk)
'''{sub(/\r$/,"")}''
''{sub(/^127\.0\.0\.1/,"0.0.0.0")}''
''BEGIN { OFS = "" }''
@@ -32,6 +32,7 @@ stdenvNoCC.mkDerivation rec {
'';
homepage = "https://github.com/StevenBlack/hosts";
license = licenses.mit;
+ maintainers = with maintainers; [ ambroisie ];
platforms = platforms.all;
};
}
diff --git a/pkgs/unified-hosts-lists/default.nix b/pkgs/unified-hosts-lists/default.nix
index af55994..06d24ac 100644
--- a/pkgs/unified-hosts-lists/default.nix
+++ b/pkgs/unified-hosts-lists/default.nix
@@ -1,16 +1,16 @@
-{ fetchFromGitHub, lib, stdenvNoCC }:
+{ lib, fetchFromGitHub, stdenvNoCC }:
stdenvNoCC.mkDerivation rec {
pname = "unified-hosts-lists";
- version = "3.6.4";
+ version = "3.12.15";
src = fetchFromGitHub {
owner = "StevenBlack";
repo = "hosts";
rev = version;
- sha256 = "sha256-U6vRwbFSYka2VS8M1z0n+FaTkKKwdV/cCWIKxp487/I=";
+ hash = "sha256-HoNX57lCoIr36B/7HMuazWSWeAPPfWY1oZf6dXnxYIE=";
};
- phases = [ "installPhase" ];
+ dontUnpack = true;
installPhase = ''
mkdir -p $out
@@ -28,6 +28,7 @@ stdenvNoCC.mkDerivation rec {
'';
homepage = "https://github.com/StevenBlack/hosts";
license = licenses.mit;
+ maintainers = with maintainers; [ ambroisie ];
platforms = platforms.all;
};
}
diff --git a/pkgs/vimix-cursors/default.nix b/pkgs/vimix-cursors/default.nix
new file mode 100644
index 0000000..80424de
--- /dev/null
+++ b/pkgs/vimix-cursors/default.nix
@@ -0,0 +1,44 @@
+{ lib, python3, fetchFromGitHub, inkscape, stdenvNoCC, xcursorgen }:
+let
+ py = python3.withPackages (ps: with ps; [ cairosvg ]);
+in
+stdenvNoCC.mkDerivation rec {
+ pname = "vimix-cursors";
+ version = "unstable-2020-04-28";
+
+ src = fetchFromGitHub {
+ owner = "vinceliuice";
+ repo = pname;
+ rev = "27ebb1935944bc986bf8ae85ee3343b8351d9823";
+ hash = "sha256-bIPRrKaNQ2Eo+T6zv7qeA1z7uRHXezM0yxh+uqA01Gs=";
+ };
+
+ nativeBuildInputs = [
+ inkscape
+ py
+ xcursorgen
+ ];
+
+ postPatch = ''
+ patchShebangs .
+ '';
+
+ buildPhase = ''
+ HOME="$NIX_BUILD_ROOT" ./build.sh
+ '';
+
+ installPhase = ''
+ install -dm 755 $out/share/icons
+ for color in "" "-white"; do
+ cp -pr dist''${color}/ "$out/share/icons/Vimix''${color}-cursors"
+ done
+ '';
+
+ meta = with lib; {
+ description = "An X cursor theme inspired by Materia design";
+ homepage = "https://github.com/vinceliuice/Vimix-cursors";
+ license = licenses.gpl3Only;
+ maintainers = with maintainers; [ ambroisie ];
+ platforms = platforms.linux;
+ };
+}
diff --git a/pkgs/volantes-cursors/default.nix b/pkgs/volantes-cursors/default.nix
new file mode 100644
index 0000000..b2c7865
--- /dev/null
+++ b/pkgs/volantes-cursors/default.nix
@@ -0,0 +1,44 @@
+{ lib, fetchFromGitHub, inkscape, stdenvNoCC, xcursorgen }:
+stdenvNoCC.mkDerivation rec {
+ pname = "volantes-cursors";
+ version = "unstable-2020-06-06";
+
+ src = fetchFromGitHub {
+ owner = "varlesh";
+ repo = pname;
+ rev = "d1d290ff42cc4fa643716551bd0b02582b90fd2f";
+ hash = "sha256-irMN/enoo90nYLfvSOScZoYdvhZKvqqp+grZB2BQD9o=";
+ };
+
+ nativeBuildInputs = [
+ inkscape
+ xcursorgen
+ ];
+
+ postPatch = ''
+ patchShebangs .
+ # The script tries to build in its source directory...
+ substituteInPlace build.sh --replace \
+ ': "''${BUILD_DIR:="$SCRIPT_DIR"/build}"' \
+ "BUILD_DIR=$(pwd)/build"
+ substituteInPlace build.sh --replace \
+ ': "''${OUT_DIR:="$SCRIPT_DIR"/dist}"' \
+ "OUT_DIR=$(pwd)/dist"
+ '';
+
+ buildPhase = ''
+ HOME="$NIX_BUILD_ROOT" ./build.sh
+ '';
+
+ installPhase = ''
+ make install PREFIX= DESTDIR=$out/
+ '';
+
+ meta = with lib; {
+ description = "Classic cursor with a flying style";
+ homepage = "https://github.com/varlesh/volantes-cursors";
+ license = licenses.gpl2Only;
+ maintainers = with maintainers; [ ambroisie ];
+ platforms = platforms.linux;
+ };
+}
diff --git a/pkgs/wifi-qr/default.nix b/pkgs/wifi-qr/default.nix
new file mode 100644
index 0000000..88164e5
--- /dev/null
+++ b/pkgs/wifi-qr/default.nix
@@ -0,0 +1,81 @@
+{ lib
+, fetchFromGitHub
+, gnome
+, installShellFiles
+, makeWrapper
+, networkmanager
+, qrencode
+, stdenvNoCC
+, xdg-utils
+, zbar
+}:
+stdenvNoCC.mkDerivation rec {
+ pname = "wifi-qr";
+ version = "unstable-2023-04-19";
+
+ outputs = [ "out" "man" ];
+
+ src = fetchFromGitHub {
+ owner = "kokoye2007";
+ repo = "wifi-qr";
+ rev = "b81d4a44257252f07e745464879aa5618ae3d434";
+ hash = "sha256-oGTAr+raJGpK4PV4GdBxX8fIUE8gcbXw7W0SvQJAee0=";
+ };
+
+ nativeBuildInputs = [
+ installShellFiles
+ makeWrapper
+ ];
+
+ dontBuild = true;
+
+ dontConfigure = true;
+
+ postPatch = ''
+ substituteInPlace wifi-qr.desktop \
+ --replace "Exec=sh -c 'wifi-qr g'" "Exec=$out/bin/wifi-qr g" \
+ --replace "Exec=sh -c 'wifi-qr q'" "Exec=$out/bin/wifi-qr q" \
+ --replace "Exec=sh -c 'wifi-qr p'" "Exec=$out/bin/wifi-qr p" \
+ --replace "Exec=sh -c 'wifi-qr c'" "Exec=$out/bin/wifi-qr c" \
+ --replace "Icon=wifi-qr.svg" "Icon=wifi-qr"
+ '';
+
+ installPhase = ''
+ runHook preInstall
+
+ install -Dm755 wifi-qr $out/bin/wifi-qr
+
+ install -Dm644 wifi-qr.desktop $out/share/applications/wifi-qr.desktop
+ install -Dm644 wifi-qr.svg $out/share/icons/hicolor/scalable/apps/wifi-qr.svg
+
+ installManPage wifi-qr.1
+
+ runHook postInstall
+ '';
+
+ wrapperPath = lib.makeBinPath [
+ gnome.zenity
+ networkmanager
+ qrencode
+ xdg-utils
+ zbar
+ ];
+
+ fixupPhase = ''
+ runHook preFixup
+
+ patchShebangs $out/bin/wifi-qr
+ wrapProgram $out/bin/wifi-qr --suffix PATH : "${wrapperPath}"
+
+ runHook postFixup
+ '';
+
+ meta = with lib; {
+ description = "WiFi password sharing via QR codes";
+ homepage = "https://github.com/kokoye2007/wifi-qr";
+ license = with licenses; [ gpl3Plus ];
+ mainProgram = "wifi-qr";
+ maintainers = with maintainers; [ ambroisie ];
+ platforms = platforms.linux;
+ };
+}
diff --git a/profiles/bluetooth/default.nix b/profiles/bluetooth/default.nix
new file mode 100644
index 0000000..292d0d1
--- /dev/null
+++ b/profiles/bluetooth/default.nix
@@ -0,0 +1,15 @@
+{ config, lib, ... }:
+let
+ cfg = config.my.profiles.bluetooth;
+in
+{
+ options.my.profiles.bluetooth = with lib; {
+ enable = mkEnableOption "bluetooth profile";
+ };
+
+ config = lib.mkIf cfg.enable {
+ my.hardware.bluetooth.enable = true;
+
+ my.home.bluetooth.enable = true;
+ };
+}
diff --git a/profiles/default.nix b/profiles/default.nix
new file mode 100644
index 0000000..f7914a1
--- /dev/null
+++ b/profiles/default.nix
@@ -0,0 +1,13 @@
+# Configuration that spans accross system and home, or are almagations of modules
+{ ... }:
+{
+ imports = [
+ ./bluetooth
+ ./devices
+ ./gtk
+ ./laptop
+ ./printing
+ ./wm
+ ./x
+ ];
+}
diff --git a/profiles/devices/default.nix b/profiles/devices/default.nix
new file mode 100644
index 0000000..7dbd299
--- /dev/null
+++ b/profiles/devices/default.nix
@@ -0,0 +1,20 @@
+{ config, lib, ... }:
+let
+ cfg = config.my.profiles.devices;
+in
+{
+ options.my.profiles.devices = with lib; {
+ enable = mkEnableOption "devices profile";
+ };
+
+ config = lib.mkIf cfg.enable {
+ my.hardware = {
+ ergodox.enable = true;
+
+ mx-ergo.enable = true;
+ };
+
+ # MTP devices auto-mount via file explorers
+ services.gvfs.enable = true;
+ };
+}
diff --git a/profiles/gtk/default.nix b/profiles/gtk/default.nix
new file mode 100644
index 0000000..a8d6d9a
--- /dev/null
+++ b/profiles/gtk/default.nix
@@ -0,0 +1,17 @@
+{ config, lib, ... }:
+let
+ cfg = config.my.profiles.gtk;
+in
+{
+ options.my.profiles.gtk = with lib; {
+ enable = mkEnableOption "gtk profile";
+ };
+
+ config = lib.mkIf cfg.enable {
+ # Allow setting GTK configuration using home-manager
+ programs.dconf.enable = true;
+
+ # GTK theme configuration
+ my.home.gtk.enable = true;
+ };
+}
diff --git a/profiles/laptop/default.nix b/profiles/laptop/default.nix
new file mode 100644
index 0000000..20a29d7
--- /dev/null
+++ b/profiles/laptop/default.nix
@@ -0,0 +1,23 @@
+{ config, lib, ... }:
+let
+ cfg = config.my.profiles.laptop;
+in
+{
+ options.my.profiles.laptop = with lib; {
+ enable = mkEnableOption "laptop profile";
+ };
+
+ config = lib.mkIf cfg.enable {
+ # Enable touchpad support
+ services.xserver.libinput.enable = true;
+
+ # Enable TLP power management
+ my.services.tlp.enable = true;
+
+ # Enable upower power management
+ my.hardware.upower.enable = true;
+
+ # Enable battery notifications
+ my.home.power-alert.enable = true;
+ };
+}
diff --git a/profiles/printing/default.nix b/profiles/printing/default.nix
new file mode 100644
index 0000000..9965797
--- /dev/null
+++ b/profiles/printing/default.nix
@@ -0,0 +1,69 @@
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.my.profiles.printing;
+in
+{
+ options.my.profiles.printing = with lib; {
+ enable = mkEnableOption "printing profile";
+
+ papersize = mkOption {
+ type = with types; either str (enum [
+ "a3"
+ "a4"
+ "a5"
+ "b5"
+ "letter"
+ "legal"
+ "executive"
+ "note"
+ "11x17"
+ ]);
+ default = "a4";
+ example = "paper";
+ description = "preferred paper size";
+ };
+
+ usb = {
+ enable = my.mkDisableOption "USB printers";
+ };
+
+ network = {
+ enable = my.mkDisableOption "network printers";
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ # Setup CUPS
+ services.printing = {
+ enable = true;
+
+ # Drivers are deprecated, but just in case
+ drivers = with pkgs; [
+ gutenprint # Base set of drivers
+ brlaser # Brother drivers
+
+ # Brother MFC-L3770CDW
+ mfcl3770cdwlpr
+ mfcl3770cdwcupswrapper
+ ];
+ };
+
+ # Setup paper size
+ systemd.services.cups.serviceConfig.Environment = [
+ "PAPERSIZE=${cfg.papersize}"
+ ];
+
+ # Allow using USB printers
+ services.ipp-usb = lib.mkIf cfg.usb.enable {
+ enable = true;
+ };
+
+ # Allow using WiFi printers
+ services.avahi = lib.mkIf cfg.network.enable {
+ enable = true;
+ openFirewall = true;
+ # Allow resolution of '.local' addresses
+ nssmdns = true;
+ };
+ };
+}
diff --git a/profiles/wm/default.nix b/profiles/wm/default.nix
new file mode 100644
index 0000000..c227328
--- /dev/null
+++ b/profiles/wm/default.nix
@@ -0,0 +1,29 @@
+{ config, lib, ... }:
+let
+ cfg = config.my.profiles.wm;
+in
+{
+ options.my.profiles.wm = with lib; {
+ windowManager = mkOption {
+ type = with types; nullOr (enum [ "i3" ]);
+ default = null;
+ example = "i3";
+ description = "Which window manager to use";
+ };
+ };
+
+ config = lib.mkMerge [
+ (lib.mkIf (cfg.windowManager == "i3") {
+ # Enable i3
+ services.xserver.windowManager.i3.enable = true;
+ # i3 settings
+ my.home.wm.windowManager = "i3";
+ # Screenshot tool
+ my.home.flameshot.enable = true;
+ # Auto disk mounter
+ my.home.udiskie.enable = true;
+ # udiskie fails if it can't find this dbus service
+ services.udisks2.enable = true;
+ })
+ ];
+}
diff --git a/profiles/x/default.nix b/profiles/x/default.nix
new file mode 100644
index 0000000..ea77939
--- /dev/null
+++ b/profiles/x/default.nix
@@ -0,0 +1,23 @@
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.my.profiles.x;
+in
+{
+ options.my.profiles.x = with lib; {
+ enable = mkEnableOption "X profile";
+ };
+
+ config = lib.mkIf cfg.enable {
+ # Enable the X11 windowing system.
+ services.xserver.enable = true;
+ # Nice wallpaper
+ services.xserver.displayManager.lightdm.background =
+ let
+ wallpapers = "${pkgs.plasma5Packages.plasma-workspace-wallpapers}/share/wallpapers";
+ in
+ "${wallpapers}/summer_1am/contents/images/2560x1600.jpg";
+
+ # X configuration
+ my.home.x.enable = true;
+ };
+}
diff --git a/secrets/.gitattributes b/secrets/.gitattributes
deleted file mode 100644
index a741d4d..0000000
--- a/secrets/.gitattributes
+++ /dev/null
@@ -1,3 +0,0 @@
-* filter=git-crypt diff=git-crypt
-.gitattributes !filter !diff
-/default.nix !filter !diff
diff --git a/secrets/acme/key.env b/secrets/acme/key.env
deleted file mode 100644
index 061d6c1..0000000
Binary files a/secrets/acme/key.env and /dev/null differ
diff --git a/secrets/backup/credentials.env b/secrets/backup/credentials.env
deleted file mode 100644
index 5b75142..0000000
Binary files a/secrets/backup/credentials.env and /dev/null differ
diff --git a/secrets/backup/password.txt b/secrets/backup/password.txt
deleted file mode 100644
index a8f640c..0000000
Binary files a/secrets/backup/password.txt and /dev/null differ
diff --git a/secrets/canary b/secrets/canary
deleted file mode 100644
index e910ea3..0000000
Binary files a/secrets/canary and /dev/null differ
diff --git a/secrets/default.nix b/secrets/default.nix
deleted file mode 100644
index 0028899..0000000
--- a/secrets/default.nix
+++ /dev/null
@@ -1,52 +0,0 @@
-{ lib, pkgs, ... }:
-
-with lib;
-let
- canaryHash = builtins.hashFile "sha256" ./canary;
- expectedHash =
- "9df8c065663197b5a1095122d48e140d3677d860343256abd5ab6e4fb4c696ab";
-in
-if canaryHash != expectedHash then
- abort "Secrets are not readable. Have you run `git-crypt unlock`?"
-else {
- options.my.secrets = mkOption {
- type = types.attrs;
- };
-
- config.my.secrets = {
- acme.key = fileContents ./acme/key.env;
-
- backup = {
- password = fileContents ./backup/password.txt;
- credentials = readFile ./backup/credentials.env;
- };
-
- drone = {
- gitea = readFile ./drone/gitea.env;
- secret = readFile ./drone/secret.env;
- ssh = {
- publicKey = readFile ./drone/ssh/key.pub;
- privateKey = readFile ./drone/ssh/key;
- };
- };
-
- lohr.secret = fileContents ./lohr/secret.txt;
-
- matrix.secret = fileContents ./matrix/secret.txt;
-
- miniflux.password = fileContents ./miniflux/password.txt;
-
- nextcloud.password = fileContents ./nextcloud/password.txt;
-
- podgrab.password = fileContents ./podgrab/password.txt;
-
- transmission.password = fileContents ./transmission/password.txt;
-
- users = {
- ambroisie.hashedPassword = fileContents ./users/ambroisie/password.txt;
- root.hashedPassword = fileContents ./users/root/password.txt;
- };
-
- wireguard = pkgs.callPackage ./wireguard { };
- };
-}
diff --git a/secrets/drone/gitea.env b/secrets/drone/gitea.env
deleted file mode 100644
index 82b190c..0000000
Binary files a/secrets/drone/gitea.env and /dev/null differ
diff --git a/secrets/drone/secret.env b/secrets/drone/secret.env
deleted file mode 100644
index 647d161..0000000
Binary files a/secrets/drone/secret.env and /dev/null differ
diff --git a/secrets/drone/ssh/key b/secrets/drone/ssh/key
deleted file mode 100644
index 1b70a14..0000000
Binary files a/secrets/drone/ssh/key and /dev/null differ
diff --git a/secrets/drone/ssh/key.pub b/secrets/drone/ssh/key.pub
deleted file mode 100644
index ca1b5e8..0000000
Binary files a/secrets/drone/ssh/key.pub and /dev/null differ
diff --git a/secrets/lohr/secret.txt b/secrets/lohr/secret.txt
deleted file mode 100644
index cbc3a26..0000000
Binary files a/secrets/lohr/secret.txt and /dev/null differ
diff --git a/secrets/matrix/secret.txt b/secrets/matrix/secret.txt
deleted file mode 100644
index ce64730..0000000
Binary files a/secrets/matrix/secret.txt and /dev/null differ
diff --git a/secrets/miniflux/password.txt b/secrets/miniflux/password.txt
deleted file mode 100644
index 482d1b7..0000000
Binary files a/secrets/miniflux/password.txt and /dev/null differ
diff --git a/secrets/nextcloud/password.txt b/secrets/nextcloud/password.txt
deleted file mode 100644
index c2e458c..0000000
Binary files a/secrets/nextcloud/password.txt and /dev/null differ
diff --git a/secrets/podgrab/password.txt b/secrets/podgrab/password.txt
deleted file mode 100644
index 81da33c..0000000
Binary files a/secrets/podgrab/password.txt and /dev/null differ
diff --git a/secrets/transmission/password.txt b/secrets/transmission/password.txt
deleted file mode 100644
index b1b7c2a..0000000
Binary files a/secrets/transmission/password.txt and /dev/null differ
diff --git a/secrets/users/ambroisie/password.txt b/secrets/users/ambroisie/password.txt
deleted file mode 100644
index 65fbdfb..0000000
Binary files a/secrets/users/ambroisie/password.txt and /dev/null differ
diff --git a/secrets/users/root/password.txt b/secrets/users/root/password.txt
deleted file mode 100644
index 6fe87e9..0000000
Binary files a/secrets/users/root/password.txt and /dev/null differ
diff --git a/secrets/wireguard/.gitattributes b/secrets/wireguard/.gitattributes
deleted file mode 100644
index d4bba55..0000000
--- a/secrets/wireguard/.gitattributes
+++ /dev/null
@@ -1 +0,0 @@
-/default.nix filter diff
diff --git a/secrets/wireguard/aramis/public.key b/secrets/wireguard/aramis/public.key
deleted file mode 100644
index 892536e..0000000
Binary files a/secrets/wireguard/aramis/public.key and /dev/null differ
diff --git a/secrets/wireguard/aramis/secret.key b/secrets/wireguard/aramis/secret.key
deleted file mode 100644
index 5f858e4..0000000
Binary files a/secrets/wireguard/aramis/secret.key and /dev/null differ
diff --git a/secrets/wireguard/default.nix b/secrets/wireguard/default.nix
deleted file mode 100644
index 1dbde9f..0000000
--- a/secrets/wireguard/default.nix
+++ /dev/null
@@ -1,30 +0,0 @@
-{ lib, ... }:
-let
- peerSpec = {
- # "Server"
- porthos = {
- clientNum = 1;
- externalIp = "91.121.177.163";
- };
-
- # "Clients"
- aramis = {
- clientNum = 2;
- };
-
- richelieu = {
- clientNum = 3;
- };
- };
-
- makePeer = name: attrs: with lib; {
- inherit (attrs) clientNum;
- publicKey = fileContents (./. + "/${name}/public.key");
- privateKey = fileContents (./. + "/${name}/secret.key");
- } // optionalAttrs (attrs ? externalIp) {
- inherit (attrs) externalIp;
- };
-in
-{
- peers = builtins.mapAttrs makePeer peerSpec;
-}
diff --git a/secrets/wireguard/porthos/public.key b/secrets/wireguard/porthos/public.key
deleted file mode 100644
index d89e768..0000000
Binary files a/secrets/wireguard/porthos/public.key and /dev/null differ
diff --git a/secrets/wireguard/porthos/secret.key b/secrets/wireguard/porthos/secret.key
deleted file mode 100644
index 1ecc84b..0000000
Binary files a/secrets/wireguard/porthos/secret.key and /dev/null differ
diff --git a/secrets/wireguard/richelieu/public.key b/secrets/wireguard/richelieu/public.key
deleted file mode 100644
index 2ad8bbc..0000000
Binary files a/secrets/wireguard/richelieu/public.key and /dev/null differ
diff --git a/secrets/wireguard/richelieu/secret.key b/secrets/wireguard/richelieu/secret.key
deleted file mode 100644
index 8b351b6..0000000
Binary files a/secrets/wireguard/richelieu/secret.key and /dev/null differ
diff --git a/services/blog.nix b/services/blog.nix
deleted file mode 100644
index 0a5fcf1..0000000
--- a/services/blog.nix
+++ /dev/null
@@ -1,39 +0,0 @@
-# My blog setup
-{ config, lib, ... }:
-let
- cfg = config.my.services.blog;
- domain = config.networking.domain;
-
- makeHostInfo = name: {
- name = "${name}.${domain}";
- value = "/var/www/${name}";
- };
-
- hostsInfo = [
- {
- name = domain;
- value = "/var/www/blog";
- }
- ] ++ builtins.map makeHostInfo [ "cv" "dev" "key" ];
-
- hosts = builtins.listToAttrs hostsInfo;
-
- makeVirtualHost = with lib.attrsets;
- name: root: nameValuePair "${name}" {
- forceSSL = true;
- useACMEHost = domain;
- inherit root;
- # Make my blog the default landing site
- default = (name == domain);
- };
-in
-{
- options.my.services.blog = {
- enable = lib.mkEnableOption "Blog hosting";
- };
-
- config = lib.mkIf cfg.enable {
- services.nginx.virtualHosts = with lib.attrsets;
- mapAttrs' makeVirtualHost hosts;
- };
-}
diff --git a/services/default.nix b/services/default.nix
deleted file mode 100644
index fc377d9..0000000
--- a/services/default.nix
+++ /dev/null
@@ -1,28 +0,0 @@
-{ ... }:
-
-{
- imports = [
- ./adblock.nix
- ./backup.nix
- ./blog.nix
- ./calibre-web.nix
- ./drone.nix
- ./gitea.nix
- ./indexers.nix
- ./jellyfin.nix
- ./lohr.nix
- ./matrix.nix
- ./miniflux.nix
- ./nextcloud.nix
- ./nginx.nix
- ./pirate.nix
- ./podgrab.nix
- ./postgresql-backup.nix
- ./quassel.nix
- ./rss-bridge.nix
- ./sabnzbd.nix
- ./ssh-server.nix
- ./transmission.nix
- ./wireguard.nix
- ];
-}
diff --git a/services/drone.nix b/services/drone.nix
deleted file mode 100644
index 50119ac..0000000
--- a/services/drone.nix
+++ /dev/null
@@ -1,196 +0,0 @@
-# A docker-based CI/CD system
-#
-# Inspired by [1]
-# [1]: https://github.com/Mic92/dotfiles/blob/master/nixos/eve/modules/drone.nix
-{ config, lib, pkgs, ... }:
-let
- cfg = config.my.services.drone;
-
- domain = config.networking.domain;
- droneDomain = "drone.${domain}";
-
- hasRunner = (name: builtins.elem name cfg.runners);
-
- execPkg = pkgs.drone-runner-exec;
-
- dockerPkg = pkgs.drone-runner-docker;
-in
-{
- options.my.services.drone = with lib; {
- enable = mkEnableOption "Drone CI";
- runners = mkOption {
- type = with types; listOf (enum [ "exec" "docker" ]);
- default = [ ];
- example = [ "exec" "docker" ];
- description = "Types of runners to enable";
- };
- admin = mkOption {
- type = types.str;
- default = "ambroisie";
- example = "admin";
- description = "Name of the admin user";
- };
- port = mkOption {
- type = types.port;
- default = 3030;
- example = 8080;
- description = "Internal port of the Drone UI";
- };
- secretFile = mkOption {
- type = types.str;
- example = "/run/secrets/drone-gitea.env";
- description = "Secrets to inject into Drone server";
- };
- sharedSecretFile = mkOption {
- type = types.str;
- example = "/run/secrets/drone-rpc.env";
- description = "Shared RPC secret to inject into server and runners";
- };
- };
-
- config = lib.mkIf cfg.enable {
- systemd.services.drone-server = {
- wantedBy = [ "multi-user.target" ];
- after = [ "postgresql.service" ];
- serviceConfig = {
- EnvironmentFile = [
- cfg.secretFile
- cfg.sharedSecretFile
- ];
- Environment = [
- "DRONE_DATABASE_DATASOURCE=postgres:///drone?host=/run/postgresql"
- "DRONE_SERVER_HOST=${droneDomain}"
- "DRONE_SERVER_PROTO=https"
- "DRONE_DATABASE_DRIVER=postgres"
- "DRONE_SERVER_PORT=:${toString cfg.port}"
- "DRONE_USER_CREATE=username:${cfg.admin},admin:true"
- "DRONE_JSONNET_ENABLED=true"
- "DRONE_STARLARK_ENABLED=true"
- ];
- ExecStart = "${pkgs.drone}/bin/drone-server";
- User = "drone";
- Group = "drone";
- };
- };
-
- users.users.drone = {
- isSystemUser = true;
- createHome = true;
- group = "drone";
- };
- users.groups.drone = { };
-
- services.postgresql = {
- ensureDatabases = [ "drone" ];
- ensureUsers = [{
- name = "drone";
- ensurePermissions = {
- "DATABASE drone" = "ALL PRIVILEGES";
- };
- }];
- };
-
- services.nginx.virtualHosts."${droneDomain}" = {
- forceSSL = true;
- useACMEHost = domain;
-
- locations."/".proxyPass = "http://127.0.0.1:${toString cfg.port}";
- };
-
- # Docker runner
- systemd.services.drone-runner-docker = lib.mkIf (hasRunner "docker") {
- wantedBy = [ "multi-user.target" ];
- after = [ "docker.socket" ]; # Needs the socket to be available
- # might break deployment
- restartIfChanged = false;
- confinement.enable = true;
- serviceConfig = {
- Environment = [
- "DRONE_SERVER_HOST=${droneDomain}"
- "DRONE_SERVER_PROTO=https"
- "DRONE_RUNNER_CAPACITY=10"
- "CLIENT_DRONE_RPC_HOST=127.0.0.1:${toString cfg.port}"
- ];
- BindPaths = [
- "/var/run/docker.sock"
- ];
- EnvironmentFile = [
- cfg.sharedSecretFile
- ];
- ExecStart = "${dockerPkg}/bin/drone-runner-docker";
- User = "drone-runner-docker";
- Group = "drone-runner-docker";
- };
- };
-
- # Make sure it is activated in that case
- virtualisation.docker.enable = lib.mkIf (hasRunner "docker") true;
-
- users.users.drone-runner-docker = lib.mkIf (hasRunner "docker") {
- isSystemUser = true;
- group = "drone-runner-docker";
- extraGroups = [ "docker" ]; # Give access to the daemon
- };
- users.groups.drone-runner-docker = lib.mkIf (hasRunner "docker") { };
-
- # Exec runner
- systemd.services.drone-runner-exec = lib.mkIf (hasRunner "exec") {
- wantedBy = [ "multi-user.target" ];
- # might break deployment
- restartIfChanged = false;
- confinement.enable = true;
- confinement.packages = with pkgs; [
- git
- gnutar
- bash
- nixUnstable
- gzip
- ];
- path = with pkgs; [
- git
- gnutar
- bash
- nixUnstable
- gzip
- ];
- serviceConfig = {
- Environment = [
- "DRONE_SERVER_HOST=${droneDomain}"
- "DRONE_SERVER_PROTO=https"
- "DRONE_RUNNER_CAPACITY=10"
- "CLIENT_DRONE_RPC_HOST=127.0.0.1:${toString cfg.port}"
- "NIX_REMOTE=daemon"
- "PAGER=cat"
- ];
- BindPaths = [
- "/nix/var/nix/daemon-socket/socket"
- "/run/nscd/socket"
- ];
- BindReadOnlyPaths = [
- "/etc/resolv.conf:/etc/resolv.conf"
- "/etc/resolvconf.conf:/etc/resolvconf.conf"
- "/etc/passwd:/etc/passwd"
- "/etc/group:/etc/group"
- "/nix/var/nix/profiles/system/etc/nix:/etc/nix"
- "${config.environment.etc."ssl/certs/ca-certificates.crt".source}:/etc/ssl/certs/ca-certificates.crt"
- "${config.environment.etc."ssh/ssh_known_hosts".source}:/etc/ssh/ssh_known_hosts"
- "/etc/machine-id"
- # channels are dynamic paths in the nix store, therefore we need to bind mount the whole thing
- "/nix/"
- ];
- EnvironmentFile = [
- cfg.sharedSecretFile
- ];
- ExecStart = "${execPkg}/bin/drone-runner-exec";
- User = "drone-runner-exec";
- Group = "drone-runner-exec";
- };
- };
-
- users.users.drone-runner-exec = lib.mkIf (hasRunner "exec") {
- isSystemUser = true;
- group = "drone-runner-exec";
- };
- users.groups.drone-runner-exec = lib.mkIf (hasRunner "exec") { };
- };
-}
diff --git a/services/gitea.nix b/services/gitea.nix
deleted file mode 100644
index ea739d5..0000000
--- a/services/gitea.nix
+++ /dev/null
@@ -1,77 +0,0 @@
-# A low-ressource, full-featured git forge.
-{ config, lib, ... }:
-let
- cfg = config.my.services.gitea;
- domain = config.networking.domain;
- giteaDomain = "gitea.${config.networking.domain}";
-in
-{
- options.my.services.gitea = with lib; {
- enable = mkEnableOption "Gitea";
- port = mkOption {
- type = types.port;
- default = 3042;
- example = 8080;
- description = "Internal port";
- };
- };
-
- config = lib.mkIf cfg.enable {
- services.gitea = {
- enable = true;
-
- appName = "Ambroisie's forge";
- httpPort = cfg.port;
- domain = giteaDomain;
- rootUrl = "https://${giteaDomain}";
-
- user = "git";
- lfs.enable = true;
-
- useWizard = false;
- disableRegistration = true;
-
- # only send cookies via HTTPS
- cookieSecure = true;
-
- database = {
- type = "postgres"; # Automatic setup
- user = "git"; # User needs to be the same as gitea user
- };
-
- # NixOS module uses `gitea dump` to backup repositories and the database,
- # but it produces a single .zip file that's not very backup friendly.
- # I configure my backup system manually below.
- dump.enable = false;
- };
-
- users.users.git = {
- description = "Gitea Service";
- home = config.services.gitea.stateDir;
- useDefaultShell = true;
- group = "git";
-
- # The service for gitea seems to hardcode the group as
- # gitea, so, uh, just in case?
- extraGroups = [ "gitea" ];
-
- isSystemUser = true;
- };
- users.groups.git = { };
-
- # Proxy to Gitea
- services.nginx.virtualHosts."${giteaDomain}" = {
- forceSSL = true;
- useACMEHost = domain;
-
- locations."/".proxyPass = "http://127.0.0.1:${toString cfg.port}/";
- };
-
- my.services.backup = {
- paths = [
- config.services.gitea.lfs.contentDir
- config.services.gitea.repositoryRoot
- ];
- };
- };
-}
diff --git a/services/indexers.nix b/services/indexers.nix
deleted file mode 100644
index 07e0f52..0000000
--- a/services/indexers.nix
+++ /dev/null
@@ -1,44 +0,0 @@
-# Torrent and usenet meta-indexers
-{ config, lib, ... }:
-let
- cfg = config.my.services.indexers;
-
- domain = config.networking.domain;
- jackettDomain = "jackett.${config.networking.domain}";
- nzbhydraDomain = "nzbhydra.${config.networking.domain}";
-
- jackettPort = 9117;
- nzbhydraPort = 5076;
-in
-{
- options.my.services.indexers = with lib; {
- jackett.enable = mkEnableOption "Jackett torrent meta-indexer";
- nzbhydra.enable = mkEnableOption "NZBHydra2 torrent meta-indexer";
- };
-
- config = {
- services.jackett = lib.mkIf cfg.jackett.enable {
- enable = true;
- };
-
- services.nginx.virtualHosts."${jackettDomain}" =
- lib.mkIf cfg.jackett.enable {
- forceSSL = true;
- useACMEHost = domain;
-
- locations."/".proxyPass = "http://127.0.0.1:${toString jackettPort}/";
- };
-
- services.nzbhydra2 = lib.mkIf cfg.nzbhydra.enable {
- enable = true;
- };
-
- services.nginx.virtualHosts."${nzbhydraDomain}" =
- lib.mkIf cfg.nzbhydra.enable {
- forceSSL = true;
- useACMEHost = domain;
-
- locations."/".proxyPass = "http://127.0.0.1:${toString nzbhydraPort}/";
- };
- };
-}
diff --git a/services/jellyfin.nix b/services/jellyfin.nix
deleted file mode 100644
index dc48354..0000000
--- a/services/jellyfin.nix
+++ /dev/null
@@ -1,30 +0,0 @@
-# A FLOSS media server
-{ config, lib, ... }:
-let
- cfg = config.my.services.jellyfin;
- domain = config.networking.domain;
- jellyfinDomain = "jellyfin.${config.networking.domain}";
-in
-{
- options.my.services.jellyfin = {
- enable = lib.mkEnableOption "Jellyfin Media Server";
- };
-
- config = lib.mkIf cfg.enable {
- services.jellyfin = {
- enable = true;
- group = "media";
- };
-
- # Proxy to Jellyfin
- services.nginx.virtualHosts."${jellyfinDomain}" = {
- forceSSL = true;
- useACMEHost = domain;
-
- locations."/" = {
- proxyPass = "http://127.0.0.1:8096/";
- proxyWebsockets = true;
- };
- };
- };
-}
diff --git a/services/miniflux.nix b/services/miniflux.nix
deleted file mode 100644
index 035bfaf..0000000
--- a/services/miniflux.nix
+++ /dev/null
@@ -1,67 +0,0 @@
-# A minimalist, opinionated feed reader
-{ config, lib, ... }:
-let
- cfg = config.my.services.miniflux;
-
- domain = config.networking.domain;
- minifluxDomain = "reader.${config.networking.domain}";
-in
-{
- options.my.services.miniflux = with lib; {
- enable = mkEnableOption "Miniflux feed reader";
-
- username = mkOption {
- type = types.str;
- default = "Ambroisie";
- example = "username";
- description = "Name of the admin user";
- };
-
- password = mkOption {
- type = types.str;
- example = "password";
- description = "Password of the admin user";
- };
-
- privatePort = mkOption {
- type = types.port;
- default = 9876;
- example = 8080;
- description = "Internal port for webui";
- };
- };
-
- config = lib.mkIf cfg.enable {
- # The service automatically sets up the DB
- services.miniflux = {
- enable = true;
-
- adminCredentialsFile =
- # Insecure, I don't care.
- builtins.toFile "credentials.env" ''
- ADMIN_USERNAME=${cfg.username}
- ADMIN_PASSWORD=${cfg.password}
- '';
-
- config = {
- # Virtual hosts settings
- BASE_URL = "https://${minifluxDomain}";
- LISTEN_ADDR = "localhost:${toString cfg.privatePort}";
- # I want fast updates
- POLLING_FREQUENCY = "30";
- BATCH_SIZE = "50";
- # I am a hoarder
- CLEANUP_ARCHIVE_UNREAD_DAYS = "-1";
- CLEANUP_ARCHIVE_READ_DAYS = "-1";
- };
- };
-
- # Proxy to Jellyfin
- services.nginx.virtualHosts."${minifluxDomain}" = {
- forceSSL = true;
- useACMEHost = domain;
-
- locations."/".proxyPass = "http://127.0.0.1:${toString cfg.privatePort}/";
- };
- };
-}
diff --git a/services/nginx.nix b/services/nginx.nix
deleted file mode 100644
index ab90760..0000000
--- a/services/nginx.nix
+++ /dev/null
@@ -1,44 +0,0 @@
-# Configuration shamelessly stolen from [1]
-#
-# [1]: https://github.com/delroth/infra.delroth.net/blob/master/common/nginx.nix
-{ config, pkgs, lib, ... }:
-
-{
- # Whenever something defines an nginx vhost, ensure that nginx defaults are
- # properly set.
- config = lib.mkIf ((builtins.attrNames config.services.nginx.virtualHosts) != [ ]) {
- services.nginx = {
- enable = true;
- statusPage = true; # For monitoring scraping.
-
- recommendedGzipSettings = true;
- recommendedOptimisation = true;
- recommendedTlsSettings = true;
- recommendedProxySettings = true;
- };
-
- networking.firewall.allowedTCPPorts = [ 80 443 ];
-
- # Nginx needs to be able to read the certificates
- users.users.nginx.extraGroups = [ "acme" ];
-
- # Use DNS wildcard certificate
- security.acme = {
- email = "bruno.acme@belanyi.fr";
- acceptTerms = true;
- certs =
- let
- domain = config.networking.domain;
- key = config.my.secrets.acme.key;
- in
- with pkgs;
- {
- "${domain}" = {
- extraDomainNames = [ "*.${domain}" ];
- dnsProvider = "gandiv5";
- credentialsFile = writeText "key.env" key; # Unsecure, I don't care.
- };
- };
- };
- };
-}
diff --git a/services/podgrab.nix b/services/podgrab.nix
deleted file mode 100644
index 556ffff..0000000
--- a/services/podgrab.nix
+++ /dev/null
@@ -1,63 +0,0 @@
-# A simple podcast fetcher. See [1]
-#
-# [1]: https://github.com/NixOS/nixpkgs/pull/106008
-{ config, lib, pkgs, ... }:
-let
- cfg = config.my.services.podgrab;
-
- domain = config.networking.domain;
- podgrabDomain = "podgrab.${domain}";
-
- podgrabPkg = pkgs.ambroisie.podgrab;
-in
-{
- options.my.services.podgrab = with lib; {
- enable = mkEnableOption "Podgrab, a self-hosted podcast manager";
-
- passwordFile = mkOption {
- type = with types; nullOr str;
- default = null;
- example = "/run/secrets/password.env";
- description = ''
- The path to a file containing the PASSWORD environment variable
- definition for Podgrab's authentification.
- '';
- };
-
- port = mkOption {
- type = types.port;
- default = 8080;
- example = 4242;
- description = "The port on which Podgrab will listen for incoming HTTP traffic.";
- };
- };
-
- config = lib.mkIf cfg.enable {
- systemd.services.podgrab = {
- description = "Podgrab podcast manager";
- wantedBy = [ "multi-user.target" ];
- environment = {
- CONFIG = "/var/lib/podgrab/config";
- DATA = "/var/lib/podgrab/data";
- GIN_MODE = "release";
- PORT = toString cfg.port;
- };
- serviceConfig = {
- DynamicUser = true;
- EnvironmentFile = lib.optional (cfg.passwordFile != null) [
- cfg.passwordFile
- ];
- ExecStart = "${podgrabPkg}/bin/podgrab";
- WorkingDirectory = "${podgrabPkg}/share";
- StateDirectory = [ "podgrab/config" "podgrab/data" ];
- };
- };
-
- services.nginx.virtualHosts."${podgrabDomain}" = {
- forceSSL = true;
- useACMEHost = domain;
-
- locations."/".proxyPass = "http://127.0.0.1:${toString cfg.port}";
- };
- };
-}
diff --git a/services/sabnzbd.nix b/services/sabnzbd.nix
deleted file mode 100644
index ebeef8b..0000000
--- a/services/sabnzbd.nix
+++ /dev/null
@@ -1,28 +0,0 @@
-# Usenet binary client.
-{ config, lib, ... }:
-let
- cfg = config.my.services.sabnzbd;
-
- domain = config.networking.domain;
- sabnzbdDomain = "sabnzbd.${domain}";
- port = 9090; # NOTE: not declaratively set...
-in
-{
- options.my.services.sabnzbd = with lib; {
- enable = mkEnableOption "SABnzbd binary news reader";
- };
-
- config = lib.mkIf cfg.enable {
- services.sabnzbd = {
- enable = true;
- group = "media";
- };
-
- services.nginx.virtualHosts."${sabnzbdDomain}" = {
- forceSSL = true;
- useACMEHost = domain;
-
- locations."/".proxyPass = "http://127.0.0.1:${toString port}";
- };
- };
-}
diff --git a/templates/c++-cmake/.clang-format b/templates/c++-cmake/.clang-format
new file mode 100644
index 0000000..19c58aa
--- /dev/null
+++ b/templates/c++-cmake/.clang-format
@@ -0,0 +1,23 @@
+# vim: ft=yaml
+---
+BasedOnStyle: LLVM
+IndentWidth: 4
+---
+Language: Cpp
+# Force pointers to the type for C++.
+DerivePointerAlignment: false
+PointerAlignment: Left
+
+# Short functions should not be on a single line, unless empty
+AllowShortFunctionsOnASingleLine: Empty
+
+# Make them level
+AccessModifierOffset: -4
+
+# It makes more sense this way
+BreakBeforeBinaryOperators: All
+BreakBeforeTernaryOperators: true
+
+# Aesthetic
+AlignOperands: AlignAfterOperator
+---
diff --git a/templates/c++-cmake/.envrc b/templates/c++-cmake/.envrc
new file mode 100644
index 0000000..95ed6fb
--- /dev/null
+++ b/templates/c++-cmake/.envrc
@@ -0,0 +1,10 @@
+if ! has nix_direnv_version || ! nix_direnv_version 2.2.1; then
+ source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.2.1/direnvrc" "sha256-zelF0vLbEl5uaqrfIzbgNzJWGmLzCmYAkInj/LNxvKs="
+fi
+
+use flake
+
+watch_file ./flake/checks.nix
+watch_file ./flake/dev-shells.nix
+
+eval "$shellHooks"
diff --git a/templates/c++-cmake/.gitignore b/templates/c++-cmake/.gitignore
new file mode 100644
index 0000000..09ba440
--- /dev/null
+++ b/templates/c++-cmake/.gitignore
@@ -0,0 +1,7 @@
+# CMake build directories
+/build
+/build-*
+
+# Nix generated files
+/.pre-commit-config.yaml
+/result
diff --git a/templates/c++-cmake/.woodpecker/check.yml b/templates/c++-cmake/.woodpecker/check.yml
new file mode 100644
index 0000000..c3b00ef
--- /dev/null
+++ b/templates/c++-cmake/.woodpecker/check.yml
@@ -0,0 +1,26 @@
+labels:
+ type: exec
+
+pipeline:
+- name: nix flake check
+ image: bash
+ commands:
+ - nix flake check
+
+- name: notifiy
+ image: bash
+ secrets:
+ - source: matrix_homeserver
+ target: address
+ - source: matrix_roomid
+ target: room
+ - source: matrix_username
+ target: user
+ - source: matrix_password
+ target: pass
+ commands:
+ - nix run '.#matrix-notifier'
+ when:
+ status:
+ - failure
+ - success
diff --git a/templates/c++-cmake/CMakeLists.txt b/templates/c++-cmake/CMakeLists.txt
new file mode 100644
index 0000000..122d8b3
--- /dev/null
+++ b/templates/c++-cmake/CMakeLists.txt
@@ -0,0 +1,18 @@
+cmake_minimum_required(VERSION 3.10)
+project(project VERSION 0.0.0 LANGUAGES CXX)
+enable_testing()
+
+add_library(common_options INTERFACE)
+target_compile_features(common_options INTERFACE
+ cxx_std_20
+)
+target_compile_options(common_options INTERFACE
+ -Wall
+ -Wextra
+)
+target_include_directories(common_options INTERFACE
+ src
+)
+
+add_subdirectory(src)
+add_subdirectory(tests)
diff --git a/templates/c++-cmake/flake.nix b/templates/c++-cmake/flake.nix
new file mode 100644
index 0000000..cb468e7
--- /dev/null
+++ b/templates/c++-cmake/flake.nix
@@ -0,0 +1,112 @@
+{
+ description = "A C++ project";
+
+ inputs = {
+ futils = {
+ type = "github";
+ owner = "numtide";
+ repo = "flake-utils";
+ ref = "main";
+ };
+
+ nixpkgs = {
+ type = "github";
+ owner = "NixOS";
+ repo = "nixpkgs";
+ ref = "nixos-unstable";
+ };
+
+ pre-commit-hooks = {
+ type = "github";
+ owner = "cachix";
+ repo = "pre-commit-hooks.nix";
+ ref = "master";
+ inputs = {
+ flake-utils.follows = "futils";
+ nixpkgs.follows = "nixpkgs";
+ };
+ };
+ };
+
+ outputs = { self, futils, nixpkgs, pre-commit-hooks }:
+ {
+ overlays = {
+ default = final: _prev: {
+ project = with final; stdenv.mkDerivation {
+ pname = "project";
+ version = "0.0.0";
+
+ src = self;
+
+ nativeBuildInputs = with pkgs; [
+ cmake
+ ninja
+ pkg-config
+ ];
+
+ checkInputs = with pkgs; [
+ gtest
+ ];
+
+ doCheck = true;
+
+ meta = with lib; {
+ description = "A C++ project";
+ homepage = "https://gitea.belanyi.fr/ambroisie/project";
+ license = licenses.mit;
+ maintainers = with maintainers; [ ambroisie ];
+ platforms = platforms.unix;
+ };
+ };
+ };
+ };
+ } // futils.lib.eachDefaultSystem (system:
+ let
+ pkgs = import nixpkgs {
+ inherit system;
+ overlays = [
+ self.overlays.default
+ ];
+ };
+
+ pre-commit = pre-commit-hooks.lib.${system}.run {
+ src = self;
+
+ hooks = {
+ nixpkgs-fmt = {
+ enable = true;
+ };
+
+ clang-format = {
+ enable = true;
+ };
+ };
+ };
+ in
+ {
+ checks = {
+ inherit (self.packages.${system}) project;
+
+ inherit pre-commit;
+ };
+
+ devShells = {
+ default = pkgs.mkShell {
+ inputsFrom = with self.packages.${system}; [
+ project
+ ];
+
+ packages = with pkgs; [
+ clang-tools
+ ];
+
+ inherit (pre-commit) shellHook;
+ };
+ };
+
+ packages = futils.lib.flattenTree {
+ default = pkgs.project;
+ inherit (pkgs) project;
+ };
+ });
+}
diff --git a/templates/c++-cmake/src/CMakeLists.txt b/templates/c++-cmake/src/CMakeLists.txt
new file mode 100644
index 0000000..d91cef1
--- /dev/null
+++ b/templates/c++-cmake/src/CMakeLists.txt
@@ -0,0 +1,4 @@
+add_executable(project main.cc)
+target_link_libraries(project PRIVATE common_options)
+
+install(TARGETS project)
diff --git a/templates/c++-cmake/src/main.cc b/templates/c++-cmake/src/main.cc
new file mode 100644
index 0000000..5eb9e4a
--- /dev/null
+++ b/templates/c++-cmake/src/main.cc
@@ -0,0 +1,5 @@
+#include
+
+int main() {
+ std::cout << "Hello World!\n";
+}
diff --git a/templates/c++-cmake/tests/CMakeLists.txt b/templates/c++-cmake/tests/CMakeLists.txt
new file mode 100644
index 0000000..269aea0
--- /dev/null
+++ b/templates/c++-cmake/tests/CMakeLists.txt
@@ -0,0 +1 @@
+add_subdirectory(unit)
diff --git a/templates/c++-cmake/tests/unit/CMakeLists.txt b/templates/c++-cmake/tests/unit/CMakeLists.txt
new file mode 100644
index 0000000..bb94448
--- /dev/null
+++ b/templates/c++-cmake/tests/unit/CMakeLists.txt
@@ -0,0 +1,15 @@
+find_package(GTest)
+
+if (${GTest_FOUND})
+include(GoogleTest)
+
+add_executable(dummy_test dummy_test.cc)
+target_link_libraries(dummy_test PRIVATE common_options)
+
+target_link_libraries(dummy_test PRIVATE
+ GTest::gtest
+ GTest::gtest_main
+)
+
+gtest_discover_tests(dummy_test)
+endif (${GTest_FOUND})
diff --git a/templates/c++-cmake/tests/unit/dummy_test.cc b/templates/c++-cmake/tests/unit/dummy_test.cc
new file mode 100644
index 0000000..4573678
--- /dev/null
+++ b/templates/c++-cmake/tests/unit/dummy_test.cc
@@ -0,0 +1,5 @@
+#include
+
+TEST(misc, passing) {
+ ASSERT_EQ(1, 1);
+}
diff --git a/templates/c++-meson/.clang-format b/templates/c++-meson/.clang-format
new file mode 100644
index 0000000..19c58aa
--- /dev/null
+++ b/templates/c++-meson/.clang-format
@@ -0,0 +1,23 @@
+# vim: ft=yaml
+---
+BasedOnStyle: LLVM
+IndentWidth: 4
+---
+Language: Cpp
+# Force pointers to the type for C++.
+DerivePointerAlignment: false
+PointerAlignment: Left
+
+# Short functions should not be on a single line, unless empty
+AllowShortFunctionsOnASingleLine: Empty
+
+# Make them level
+AccessModifierOffset: -4
+
+# It makes more sense this way
+BreakBeforeBinaryOperators: All
+BreakBeforeTernaryOperators: true
+
+# Aesthetic
+AlignOperands: AlignAfterOperator
+---
diff --git a/templates/c++-meson/.envrc b/templates/c++-meson/.envrc
new file mode 100644
index 0000000..95ed6fb
--- /dev/null
+++ b/templates/c++-meson/.envrc
@@ -0,0 +1,10 @@
+if ! has nix_direnv_version || ! nix_direnv_version 2.2.1; then
+ source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.2.1/direnvrc" "sha256-zelF0vLbEl5uaqrfIzbgNzJWGmLzCmYAkInj/LNxvKs="
+fi
+
+use flake
+
+watch_file ./flake/checks.nix
+watch_file ./flake/dev-shells.nix
+
+eval "$shellHooks"
diff --git a/templates/c++-meson/.gitignore b/templates/c++-meson/.gitignore
new file mode 100644
index 0000000..09ba440
--- /dev/null
+++ b/templates/c++-meson/.gitignore
@@ -0,0 +1,7 @@
+# CMake build directories
+/build
+/build-*
+
+# Nix generated files
+/.pre-commit-config.yaml
+/result
diff --git a/templates/c++-meson/.woodpecker/check.yml b/templates/c++-meson/.woodpecker/check.yml
new file mode 100644
index 0000000..c3b00ef
--- /dev/null
+++ b/templates/c++-meson/.woodpecker/check.yml
@@ -0,0 +1,26 @@
+labels:
+ type: exec
+
+pipeline:
+- name: nix flake check
+ image: bash
+ commands:
+ - nix flake check
+
+- name: notifiy
+ image: bash
+ secrets:
+ - source: matrix_homeserver
+ target: address
+ - source: matrix_roomid
+ target: room
+ - source: matrix_username
+ target: user
+ - source: matrix_password
+ target: pass
+ commands:
+ - nix run '.#matrix-notifier'
+ when:
+ status:
+ - failure
+ - success
diff --git a/templates/c++-meson/flake.nix b/templates/c++-meson/flake.nix
new file mode 100644
index 0000000..9cfed0d
--- /dev/null
+++ b/templates/c++-meson/flake.nix
@@ -0,0 +1,112 @@
+{
+ description = "A C++ project";
+
+ inputs = {
+ futils = {
+ type = "github";
+ owner = "numtide";
+ repo = "flake-utils";
+ ref = "main";
+ };
+
+ nixpkgs = {
+ type = "github";
+ owner = "NixOS";
+ repo = "nixpkgs";
+ ref = "nixos-unstable";
+ };
+
+ pre-commit-hooks = {
+ type = "github";
+ owner = "cachix";
+ repo = "pre-commit-hooks.nix";
+ ref = "master";
+ inputs = {
+ flake-utils.follows = "futils";
+ nixpkgs.follows = "nixpkgs";
+ };
+ };
+ };
+
+ outputs = { self, futils, nixpkgs, pre-commit-hooks }:
+ {
+ overlays = {
+ default = final: _prev: {
+ project = with final; stdenv.mkDerivation {
+ pname = "project";
+ version = "0.0.0";
+
+ src = self;
+
+ nativeBuildInputs = with pkgs; [
+ meson
+ ninja
+ pkg-config
+ ];
+
+ checkInputs = with pkgs; [
+ gtest
+ ];
+
+ doCheck = true;
+
+ meta = with lib; {
+ description = "A C++ project";
+ homepage = "https://gitea.belanyi.fr/ambroisie/project";
+ license = licenses.mit;
+ maintainers = with maintainers; [ ambroisie ];
+ platforms = platforms.unix;
+ };
+ };
+ };
+ };
+ } // futils.lib.eachDefaultSystem (system:
+ let
+ pkgs = import nixpkgs {
+ inherit system;
+ overlays = [
+ self.overlays.default
+ ];
+ };
+
+ pre-commit = pre-commit-hooks.lib.${system}.run {
+ src = self;
+
+ hooks = {
+ nixpkgs-fmt = {
+ enable = true;
+ };
+
+ clang-format = {
+ enable = true;
+ };
+ };
+ };
+ in
+ {
+ checks = {
+ inherit (self.packages.${system}) project;
+
+ inherit pre-commit;
+ };
+
+ devShells = {
+ default = pkgs.mkShell {
+ inputsFrom = with self.packages.${system}; [
+ project
+ ];
+
+ packages = with pkgs; [
+ clang-tools
+ ];
+
+ inherit (pre-commit) shellHook;
+ };
+ };
+
+ packages = futils.lib.flattenTree {
+ default = pkgs.project;
+ inherit (pkgs) project;
+ };
+ });
+}
diff --git a/templates/c++-meson/meson.build b/templates/c++-meson/meson.build
new file mode 100644
index 0000000..bc228c6
--- /dev/null
+++ b/templates/c++-meson/meson.build
@@ -0,0 +1,13 @@
+project(
+ 'project',
+ 'cpp',
+ version : '0.0.0',
+ license: 'MIT',
+ default_options : [
+ 'cpp_std=c++20',
+ 'warning_level=3',
+ ],
+)
+
+subdir('src')
+subdir('tests')
diff --git a/templates/c++-meson/src/main.cc b/templates/c++-meson/src/main.cc
new file mode 100644
index 0000000..5eb9e4a
--- /dev/null
+++ b/templates/c++-meson/src/main.cc
@@ -0,0 +1,5 @@
+#include
+
+int main() {
+ std::cout << "Hello World!\n";
+}
diff --git a/templates/c++-meson/src/meson.build b/templates/c++-meson/src/meson.build
new file mode 100644
index 0000000..4b2ff23
--- /dev/null
+++ b/templates/c++-meson/src/meson.build
@@ -0,0 +1,8 @@
+sources = files(
+ 'main.cc',
+)
+
+executable(
+ 'project',
+ sources : sources,
+)
diff --git a/templates/c++-meson/tests/meson.build b/templates/c++-meson/tests/meson.build
new file mode 100644
index 0000000..082b746
--- /dev/null
+++ b/templates/c++-meson/tests/meson.build
@@ -0,0 +1 @@
+subdir('unit')
diff --git a/templates/c++-meson/tests/unit/dummy_test.cc b/templates/c++-meson/tests/unit/dummy_test.cc
new file mode 100644
index 0000000..4573678
--- /dev/null
+++ b/templates/c++-meson/tests/unit/dummy_test.cc
@@ -0,0 +1,5 @@
+#include
+
+TEST(misc, passing) {
+ ASSERT_EQ(1, 1);
+}
diff --git a/templates/c++-meson/tests/unit/meson.build b/templates/c++-meson/tests/unit/meson.build
new file mode 100644
index 0000000..44c8a92
--- /dev/null
+++ b/templates/c++-meson/tests/unit/meson.build
@@ -0,0 +1,13 @@
+gtest_dep = dependency(
+ 'gtest',
+ main : true,
+ required : false,
+)
+
+dummy_test = executable(
+ 'dummy_test',
+ 'dummy_test.cc',
+ dependencies : gtest_dep,
+)
+
+test('dummy test', dummy_test)
diff --git a/templates/default.nix b/templates/default.nix
new file mode 100644
index 0000000..f58fd72
--- /dev/null
+++ b/templates/default.nix
@@ -0,0 +1,10 @@
+{
+ "c++-cmake" = {
+ path = ./c++-cmake;
+ description = "A C++ project using CMake";
+ };
+ "c++-meson" = {
+ path = ./c++-meson;
+ description = "A C++ project using CMake";
+ };
+}