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