Compare commits

..

1 commit

Author SHA1 Message Date
sid
951182f6df initial commit
All checks were successful
Flake check / flake-check (pull_request) Successful in 4m23s
2026-03-30 12:35:50 +02:00
164 changed files with 1778 additions and 3412 deletions

1
.envrc
View file

@ -1,2 +1 @@
# shellcheck shell=bash
use flake

View file

@ -1,4 +1,3 @@
---
name: Build tests
on:

View file

@ -1,4 +1,3 @@
---
name: Deploy docs
on:

View file

@ -1,8 +1,6 @@
---
name: Flake check
on:
pull_request:
on: [pull_request]
jobs:
flake-check:
@ -13,8 +11,5 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4
- name: Check formatting
run: nix fmt -- --check
- name: Run flake check
run: nix flake check --impure --all-systems
run: nix flake check --impure

View file

@ -27,7 +27,7 @@ Add this repo to your flake inputs:
inputs.synix.url = "git+https://git.sid.ovh/sid/synix.git";
```
See the [documentation](https://doc.sid.ovh) for a full setup guide.
See the [documentation](https://doc.sid.ovh/synix) for a full setup guide.
## Templates

View file

@ -11,6 +11,9 @@ HOSTNAME=""
# Templates with Home Manager configurations
HM_CONFIGS=("hyprland")
# This will get overwritten by the derivation
TEMPLATES_DIR=""
# Print usage information
usage() {
cat <<EOF

View file

@ -6,7 +6,7 @@
home-manager,
hostname,
nix,
nixos-rebuild-ng,
nixos-rebuild,
...
}:
@ -25,6 +25,6 @@ writeShellApplication {
home-manager
hostname
nix
nixos-rebuild-ng
nixos-rebuild
];
}

View file

@ -1,21 +1,15 @@
#!/usr/bin/env bash
# NixOS and standalone Home Manager rebuild script
# Defaults
FLAKE_PATH="${REBUILD_FLAKE_PATH:-${FLAKE_PATH:-$HOME/.config/nixos}}" # Default flake path
USER="${REBUILD_USER:-$(whoami)}" # Default username
HOST="${REBUILD_HOST:-$(hostname)}" # Default hostname
BUILD_HOST="${REBUILD_BUILD_HOST:-}" # Default build host
TARGET_HOST="${REBUILD_TARGET_HOST:-}" # Default target host
UPDATE="${REBUILD_UPDATE:-0}" # Default to not update
UPDATE_INPUTS="${REBUILD_UPDATE_INPUTS:-}" # Default list of inputs
ROLLBACK="${REBUILD_ROLLBACK:-0}" # Default to not rollback
SHOW_TRACE="${REBUILD_SHOW_TRACE:-0}" # Default to not show trace
# Output functions
_status() { echo -e "\033[0;34m> $1\033[0m"; }
success() { echo -e "\033[0;32m$1\033[0m"; }
error() { echo -e "\033[0;31mError: $1\033[0m" >&2; exit 1; }
FLAKE_PATH="$HOME/.config/nixos" # Default flake path
HOME_USER="$(whoami)" # Default username. Used to identify the Home Manager configuration
NIXOS_HOST="$(hostname)" # Default hostname. Used to identify the NixOS and Home Manager configuration
BUILD_HOST="" # Default build host. Empty means localhost
TARGET_HOST="" # Default target host. Empty means localhost
UPDATE=0 # Default to not update flake repositories
UPDATE_INPUTS="" # Default list of inputs to update. Empty means all
ROLLBACK=0 # Default to not rollback
SHOW_TRACE=0 # Default to not show detailed error messages
# Function to display the help message
Help() {
@ -29,7 +23,7 @@ Help() {
echo " help Show this help message"
echo
echo "Options (for NixOS and Home Manager):"
echo " -H, --host <host> Specify the hostname (as in 'nixosConfiguraions.<host>'). Default: $HOST"
echo " -H, --host <host> Specify the hostname (as in 'nixosConfiguraions.<host>'). Default: $NIXOS_HOST"
echo " -p, --path <path> Set the path to the flake directory. Default: $FLAKE_PATH"
echo " -U, --update [inputs] Update all flake inputs. Optionally provide comma-separated list of inputs to update instead."
echo " -r, --rollback Don't build the new configuration, but use the previous generation instead"
@ -40,50 +34,47 @@ Help() {
echo " -T, --target-host <user@example.com> Deploy the configuration to a remote host via SSH. If '--host' is specified, it will be used as the target host."
echo
echo "Home Manager only options:"
echo " -u, --user <user> Specify the username (as in 'homeConfigurations.<user>@<host>'). Default: $USER"
echo " -u, --user <user> Specify the username (as in 'homeConfigurations.<user>@<host>'). Default: $HOME_USER"
}
# Function to handle errors
error() {
echo "Error: $1"
exit 1
}
# Function to rebuild NixOS configuration
Rebuild_nixos() {
local FLAKE="$FLAKE_PATH#$HOST"
local FLAKE="$FLAKE_PATH#$NIXOS_HOST"
# Construct rebuild command
local CMD=("nixos-rebuild" "switch" "--sudo")
[[ -n "$TARGET_HOST" || -n "$BUILD_HOST" ]] && CMD+=("--ask-sudo-password")
CMD+=("--flake" "$FLAKE")
[ "$ROLLBACK" = 1 ] && CMD+=("--rollback")
[ "$SHOW_TRACE" = 1 ] && CMD+=("--show-trace")
[ -n "$BUILD_HOST" ] && CMD+=("--build-host" "$BUILD_HOST")
if [ "$HOST" != "$(hostname)" ] && [ -z "$TARGET_HOST" ]; then
TARGET_HOST="$HOST"
_status "Using '$TARGET_HOST' as target host."
if [ "$NIXOS_HOST" != "$(hostname)" ] && [ -z "$TARGET_HOST" ]; then
TARGET_HOST="$NIXOS_HOST"
echo "Using '$TARGET_HOST' as target host."
fi
[ -n "$TARGET_HOST" ] && CMD+=("--target-host" "$TARGET_HOST")
[[ -n "$TARGET_HOST" || -n "$BUILD_HOST" ]] && CMD+=("--ask-sudo-password")
# Build config first so we can diff it
local BUILD_CMD=("nixos-rebuild" "build" "--no-build-output" "--flake" "$FLAKE")
[ "$SHOW_TRACE" = 1 ] && BUILD_CMD+=("--show-trace")
[ -n "$BUILD_HOST" ] && BUILD_CMD+=("--build-host" "$BUILD_HOST")
# Rebuild NixOS configuration
if [ "$ROLLBACK" = 0 ]; then
_status "Building NixOS configuration '$FLAKE'..."
_status "Executing command: ${BUILD_CMD[*]}"
"${BUILD_CMD[@]}" || error "NixOS build failed"
_status "Switching to new NixOS configuration"
echo "Rebuilding NixOS configuration '$FLAKE'..."
else
_status "Rolling back to last NixOS generation"
echo "Rolling back to last NixOS generation..."
fi
sudo -v
_status "Executing command: ${CMD[*]}"
echo "Executing command: ${CMD[*]}"
"${CMD[@]}" || error "NixOS rebuild failed"
success "NixOS rebuild completed successfully."
echo "NixOS rebuild completed successfully."
}
# Function to rebuild Home Manager configuration
Rebuild_home() {
local FLAKE="$FLAKE_PATH#$USER@$HOST"
local FLAKE="$FLAKE_PATH#$HOME_USER@$NIXOS_HOST"
if [ -n "$BUILD_HOST" ] || [ -n "$TARGET_HOST" ]; then
error "Remote building is not supported for Home Manager."
@ -100,27 +91,21 @@ Rebuild_home() {
[ "$SHOW_TRACE" = 1 ] && CMD+=("--show-trace")
fi
# Build config first so we can diff it
# Rebuild Home Manager configuration
if [ "$ROLLBACK" = 0 ]; then
local BUILD_CMD=("home-manager" "build" "--no-out-link" "--flake" "$FLAKE")
[ "$SHOW_TRACE" = 1 ] && BUILD_CMD+=("--show-trace")
_status "Building Home Manager configuration '$FLAKE'..."
_status "Executing command: ${BUILD_CMD[*]}"
"${BUILD_CMD[@]}" || error "Home Manager build failed"
_status "Switching to new Home Manager configuration"
echo "Rebuilding Home Manager configuration '$FLAKE'..."
else
_status "Rolling back to last Home Manager generation"
echo "Rolling back to last Home Manager generation..."
fi
_status "Executing command: ${CMD[*]}"
echo "Executing command: ${CMD[*]}"
"${CMD[@]}" || error "Home Manager rebuild failed"
success "Home Manager rebuild completed successfully."
echo "Home Manager rebuild completed successfully."
}
# Function to update flake repositories
# Function to Update flake repositories
Update() {
_status "Updating flake inputs..."
echo "Updating flake inputs..."
# Construct update command as an array
local CMD=("nix" "flake" "update" "--flake" "$FLAKE_PATH")
@ -132,18 +117,17 @@ Update() {
done
fi
_status "Executing command: ${CMD[*]}"
echo "Executing command: ${CMD[*]}"
"${CMD[@]}" || error "Failed to update flake repositories"
success "Flake repositories updated successfully."
echo "Flake repositories updated successfully."
}
# Parse command-line options
if [[ -z "${1:-}" ]]; then
echo -e "\033[0;31mError: No command specified. Printing help page.\033[0m" >&2
echo "Error: No command specified. Printing help page."
Help
exit 1
fi
COMMAND=$1
shift
@ -157,7 +141,7 @@ while [ $# -gt 0 ]; do
case "${1:-}" in
-H|--host)
if [ -n "${2:-}" ]; then
HOST="$2"
NIXOS_HOST="$2"
shift 2
else
error "-H|--host option requires an argument"
@ -165,7 +149,7 @@ while [ $# -gt 0 ]; do
;;
-u|--user)
if [ -n "${2:-}" ]; then
USER="$2"
HOME_USER="$2"
shift 2
else
error "-u|--user option requires an argument"
@ -214,7 +198,9 @@ while [ $# -gt 0 ]; do
fi
;;
*)
error "Unknown option '$1'"
echo "Error: Unknown option '$1'"
Help
exit 1
;;
esac
done
@ -241,10 +227,10 @@ fi
[ "$UPDATE" = 1 ] && Update
case "$COMMAND" in
nixos|os)
nixos)
Rebuild_nixos
;;
home|hm)
home)
Rebuild_home
;;
all)
@ -252,6 +238,9 @@ case "$COMMAND" in
Rebuild_home
;;
*)
error "Unknown command '$COMMAND'"
echo "Error: Unknown command '$COMMAND'"
echo "Printing help page:"
Help
exit 1
;;
esac

View file

@ -1,4 +1,3 @@
#!/usr/bin/env bash
SYSTEM="x86_64-linux"
IGNORE_PACKAGES=(
"pyman"

View file

@ -114,8 +114,7 @@ git clone YOUR_GIT_REPO_URL ~/.config/nixos
Home Manager is not installed by default. Enter the development shell to apply the configuration:
```bash
nix --experimental-features "nix-command flakes" develop git+https://git.sid.ovh/sid/synix#devShells.x86_64-linux.install-hm \
--command "rebuild home"
nix-shell ~/.config/nixos/shell.nix --run 'rebuild home'
```
### 8. Reboot your System

View file

@ -19,7 +19,7 @@ Provide the following entries to your `secrets.yaml`:
```yaml
headplane:
cookie_secret: abc123
api_key: abc123
agent_pre_authkey: abc123
```
Generate your cookie secret with:
@ -28,7 +28,7 @@ Generate your cookie secret with:
nix-shell -p openssl --run "openssl rand -hex 16"
```
Generate your agent `api_key` with:
Generate your agent pre-authkey with:
```bash
sudo headscale users create headplane-agent

View file

@ -0,0 +1,36 @@
# Tailscale
Private WireGuard networks made easy.
View the [*synix* NixOS module on Forgejo](https://git.sid.ovh/sid/synix/tree/master/modules/nixos/tailscale).
## References
- [Website](https://tailscale.com/)
- [GitHub](https://github.com/tailscale/tailscale)
- [Documents](https://tailscale.com/kb/1017/install)
## Sops
Provide the following entries to your `secrets.yaml`:
> Replace `abc123` with your actual secrets
```yaml
tailscale:
auth-key: abc123
```
## Config
```nix
{
imports = [ inputs.synix.nixosModules.tailscale ];
services.tailscale = {
enable = true;
enableSSH = true;
loginServer = "<your-headscale-instance>";
};
}
```

36
flake.lock generated
View file

@ -202,11 +202,11 @@
},
"flake-schemas": {
"locked": {
"lastModified": 1776384880,
"narHash": "sha256-28Cg9HI/IwFHUm5fZyMEGCQ94L3Il6K4OfHeRf0b+Zw=",
"lastModified": 1770913512,
"narHash": "sha256-jRC1qRoRCrMjDalVfUMHFlKSkkA2q0RZWTDW0LsquoA=",
"owner": "DeterminateSystems",
"repo": "flake-schemas",
"rev": "b852751d29c6e09919f25c1e10754646bdc181c9",
"rev": "3c464b0e09cb44c6e073c41dd1d834980c3e0b24",
"type": "github"
},
"original": {
@ -258,11 +258,11 @@
]
},
"locked": {
"lastModified": 1778507602,
"narHash": "sha256-kTwur1wV+01SdqskVMSo6JMEpg71ps3HpbFY2GsflKs=",
"lastModified": 1772024342,
"narHash": "sha256-+eXlIc4/7dE6EcPs9a2DaSY3fTA9AE526hGqkNID3Wg=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "61ab0e80d9c7ab14c256b5b453d8b3fb0189ba0a",
"rev": "6e34e97ed9788b17796ee43ccdbaf871a5c2b476",
"type": "github"
},
"original": {
@ -349,11 +349,11 @@
]
},
"locked": {
"lastModified": 1779506708,
"narHash": "sha256-QOD/CNm196nCJRheux/URi4/HE66fthdOMqCJoPP1Y0=",
"lastModified": 1772020340,
"narHash": "sha256-aqBl3GNpCadMoJ/hVkWTijM1Aeilc278MjM+LA3jK6g=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "3ee51fbdac8c8bdfe1e7e1fcaba6520a563f394f",
"rev": "36e38ca0d9afe4c55405fdf22179a5212243eecc",
"type": "github"
},
"original": {
@ -465,11 +465,11 @@
},
"nixpkgs_2": {
"locked": {
"lastModified": 1779467186,
"narHash": "sha256-nOesoDCiXcUftqbRBMz9tt4blI5PvljMWbm3kuCA+0s=",
"lastModified": 1771903837,
"narHash": "sha256-sdaqdnsQCv3iifzxwB22tUwN/fSHoN7j2myFW5EIkGk=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "b77b3de8775677f84492abe84635f87b0e153f0f",
"rev": "e764fc9a405871f1f6ca3d1394fb422e0a0c3951",
"type": "github"
},
"original": {
@ -511,11 +511,11 @@
]
},
"locked": {
"lastModified": 1779900588,
"narHash": "sha256-b/yda4uMmjpw4uhXI4d0JNv09WtGoXis2JjD5l1Qbts=",
"lastModified": 1772140017,
"narHash": "sha256-wFUc9tn5Ik11oL009BkVnj4NxsY63UbZjhOyEqCsIQE=",
"owner": "nix-community",
"repo": "NUR",
"rev": "dd8bdde89853bfb3eae0eb28e3d3a9320b3b8a3c",
"rev": "be0fea0af0e8cfadb3995dd7bb3a167bc012e935",
"type": "github"
},
"original": {
@ -605,11 +605,11 @@
"tinted-zed": "tinted-zed"
},
"locked": {
"lastModified": 1778680496,
"narHash": "sha256-tUq1WASV0dHLv3j18log8V6Esq0NYkXuzNH2EHsstcg=",
"lastModified": 1771788390,
"narHash": "sha256-RzBpBwn93GWxLjacTte+ngwwg0L/BVOg4G/sSIeK3Rw=",
"owner": "nix-community",
"repo": "stylix",
"rev": "fc5bec2e44678eeaa221d566d447a0257a884737",
"rev": "ebb238f14d6f930068be4718472da3105fd5d3bf",
"type": "github"
},
"original": {

224
flake.nix
View file

@ -3,6 +3,7 @@
nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11";
nix.url = "github:DeterminateSystems/nix-src/flake-schemas";
flake-schemas.url = "github:DeterminateSystems/flake-schemas";
git-hooks.url = "github:cachix/git-hooks.nix";
@ -28,69 +29,50 @@
...
}@inputs:
let
inherit (self) outputs;
systems = [
supportedSystems = [
"x86_64-linux"
"aarch64-linux" # For testing only. Use at your own risk.
];
lib = nixpkgs.lib.extend (_final: _prev: { inherit (self.lib) utils; });
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
mkPkgs =
{
system,
config ? { },
}:
nixpkgsFor = forAllSystems (
system:
import nixpkgs {
inherit system;
inherit config;
overlays = [
self.overlays.modifications
self.overlays.additions
self.overlays.default
inputs.nix.overlays.default
(final: prev: { synix = self.overlays.additions final prev; })
];
};
}
);
forAllSystems =
function:
lib.genAttrs systems (
system:
function (mkPkgs {
inherit system;
})
);
mkNixosConfiguration =
system: modules:
nixpkgs.lib.nixosSystem {
inherit system modules;
specialArgs = {
inherit inputs outputs lib;
};
};
mkHomeConfiguration =
system: modules:
inputs.home-manager.lib.homeManagerConfiguration {
pkgs = mkPkgs { inherit system; };
inherit modules;
extraSpecialArgs = {
inherit inputs outputs;
};
test = {
system = "x86_64-linux";
lib = nixpkgs.lib.extend (final: prev: self.outputs.lib or { });
inputs = inputs // {
synix = self;
};
outputs = { };
overlays = [
self.overlays.default
self.overlays.additions
self.overlays.modifications
(final: prev: { synix = self.packages."${final.system}"; })
];
};
in
{
inherit (inputs.flake-schemas) schemas;
apps = forAllSystems (
pkgs:
system:
let
mkApp = name: description: {
pkgs = nixpkgs.legacyPackages.${system};
mkApp = name: desc: {
type = "app";
program = pkgs.lib.getExe (pkgs.callPackage ./apps/${name} { });
meta.description = description;
meta.description = desc;
};
in
{
@ -104,17 +86,13 @@
);
lib = {
utils = import ./lib/utils.nix { inherit (nixpkgs) lib; };
helpers = {
inherit mkPkgs;
};
utils = import ./lib/utils.nix { lib = nixpkgs.lib; };
};
packages = forAllSystems (
pkgs:
system:
let
inherit (pkgs.stdenv.hostPlatform) system;
allArchs = import ./pkgs { inherit pkgs; };
allArchs = import ./pkgs { pkgs = nixpkgs.legacyPackages.${system}; };
x64only =
if system == "x86_64-linux" then
{
@ -133,97 +111,125 @@
# test configs
nixosConfigurations = {
nixos-hyprland = mkNixosConfiguration "x86_64-linux" [ ./tests/build/nixos-hyprland ];
nixos-server = mkNixosConfiguration "x86_64-linux" [ ./tests/build/nixos-server ];
nixos-hyprland = nixpkgs.lib.nixosSystem {
inherit (test) system;
modules = [
./tests/build/nixos-hyprland
{ nixpkgs.overlays = test.overlays; }
];
specialArgs = {
inherit (test) inputs outputs lib;
};
};
nixos-server = nixpkgs.lib.nixosSystem {
inherit (test) system;
modules = [
./tests/build/nixos-server
{ nixpkgs.overlays = test.overlays; }
];
specialArgs = {
inherit (test) inputs outputs lib;
};
};
};
homeConfigurations = {
hm-hyprland = mkHomeConfiguration "x86_64-linux" [ ./tests/build/hm-hyprland ];
hm-hyprland = inputs.home-manager.lib.homeManagerConfiguration {
pkgs = import nixpkgs {
inherit (test) overlays system;
};
extraSpecialArgs = {
inherit (test) inputs outputs;
};
modules = [
./tests/build/hm-hyprland
];
};
};
devShells = forAllSystems (
pkgs:
system:
let
inherit (pkgs.stdenv.hostPlatform) system;
inherit (self.checks.${system}.pre-commit-check) shellHook enabledPackages;
pkgs = nixpkgsFor.${system};
in
{
default = pkgs.mkShell {
inherit shellHook;
nativeBuildInputs = [
enabledPackages
pkgs.nix
]
++ (with pkgs; [
(python313.withPackages (
p: with p; [
mkdocs
mkdocs-material
mkdocs-material-extensions
pygments
]
))
]);
};
nix-config = pkgs.mkShell {
inherit shellHook;
nativeBuildInputs = [ enabledPackages ];
};
install-hm = pkgs.mkShell {
NIX_CONFIG = "extra-experimental-features = nix-command flakes";
nativeBuildInputs = [ pkgs.home-manager ];
};
default =
let
inherit (self.checks.${system}.pre-commit-check) shellHook enabledPackages;
in
pkgs.mkShell {
inherit shellHook;
nativeBuildInputs = [
enabledPackages
pkgs.nix
]
++ (with pkgs; [
(python313.withPackages (
p: with p; [
mkdocs
mkdocs-material
mkdocs-material-extensions
pygments
]
))
]);
};
}
);
formatter = forAllSystems (
pkgs:
system:
let
inherit (pkgs.stdenv.hostPlatform) system;
inherit (self.checks.${system}.pre-commit-check.config) package configFile;
pkgs = nixpkgs.legacyPackages.${system};
config = self.checks.${system}.pre-commit-check.config;
inherit (config) package configFile;
script = ''
${pkgs.lib.getExe package} run --all-files --config ${configFile}
'';
in
pkgs.writeShellScriptBin "pre-commit-run" "${pkgs.lib.getExe package} run --all-files --config ${configFile}"
pkgs.writeShellScriptBin "pre-commit-run" script
);
checks = forAllSystems (
pkgs:
system:
let
inherit (pkgs.stdenv.hostPlatform) system;
pkgs = nixpkgs.legacyPackages.${system};
flakePkgs = self.packages.${system};
overlaidPkgs = import nixpkgs {
inherit system;
overlays = [ self.overlays.modifications ];
};
in
{
pre-commit-check = inputs.git-hooks.lib.${system}.run {
src = ./.;
hooks = {
actionlint.enable = true;
nixfmt = {
enable = true;
settings.width = 120;
};
shellcheck.enable = true;
statix.enable = true;
yamllint = {
enable = true;
excludes = [ "secrets.yaml" ];
settings.configData = "{rules: {line-length: {max: 120}}}";
};
nixfmt.enable = true;
};
};
build-additions = pkgs.linkFarm "added-packages-${system}" self.packages.${system};
build-modifications = pkgs.linkFarm "modified-packages-${system}" (
lib.filterAttrs (_: v: lib.isDerivation v) (self.overlays.modifications pkgs pkgs)
);
build-packages = pkgs.linkFarm "flake-packages-${system}" flakePkgs;
build-overlays = pkgs.linkFarm "flake-overlays-${system}" {
kicad = overlaidPkgs.kicad;
};
synapse-test =
let
testPkgs = mkPkgs {
testPkgs = import nixpkgs {
inherit system;
config.permittedInsecurePackages = [ "olm-3.2.16" ];
};
in
testPkgs.testers.runNixOSTest ./tests/run/synapse.nix;
open-webui-oci-test = pkgs.testers.runNixOSTest ./tests/run/open-webui-oci.nix;
# librechat-oci-test = pkgs.testers.runNixOSTest ./tests/run/librechat-oci.nix; # FIXME: unable to copy from source docker://quay.io/mongo:7.0
# NOTE: disabled for now since the test takes too long to execute
# open-webui-oci-test =
# let
# testPkgs = import nixpkgs {
# inherit system;
# };
# in
# testPkgs.testers.runNixOSTest ./tests/run/open-webui-oci.nix;
}
);
hydraJobs = {
@ -283,10 +289,6 @@
path = ./templates/dev/rs-hello;
description = "Rust hello world template.";
};
stm32-blink = {
path = ./templates/dev/stm32-blink;
description = "STM32G4 blink template with libopencm3.";
};
};
};
}

View file

@ -1,4 +1,3 @@
---
site_name: synix docs
repo_url: https://git.sid.ovh/sid/synix
site_url: https://doc.sid.ovh/synix
@ -21,7 +20,7 @@ markdown_extensions:
- pymdownx.superfences
nav:
- Home: index.md # do not change
- Home: index.md # do not change
- Introduction to Nix:
- Overview: introduction-to-nix/overview.md
- Install Nix: introduction-to-nix/install-nix.md
@ -59,6 +58,7 @@ nav:
- radicale: modules/nixos/radicale.md
- rss-bridge: modules/nixos/rss-bridge.md
- sops: modules/nixos/sops.md
- tailscale: modules/nixos/tailscale.md
- virtualisation: modules/nixos/virtualisation.md
- webPage: modules/nixos/webpage.md
- Home Manager:

View file

@ -1,4 +1,3 @@
#!/usr/bin/env bash
# change directory with fzf
# Usage: cdf [optional_relative_path]
# - If no argument, searches from $HOME.
@ -68,8 +67,7 @@ function cdf() {
"--preview=tree -C {} | head -50"
"--preview-window=right:50%:wrap"
)
local selected
selected=$(find "${find_args[@]}" 2>/dev/null | fzf "${fzf_args[@]}")
local selected=$(find "${find_args[@]}" 2>/dev/null | fzf "${fzf_args[@]}")
if [[ -n "$selected" ]]; then
cd "$selected" || echo "Failed to cd into '$selected'"

View file

@ -1,48 +0,0 @@
{
inputs,
config,
lib,
pkgs,
...
}:
let
cfg = config.wayland.windowManager.hyprland;
app = cfg.applications.password-manager.default;
inherit (lib) mkDefault mkIf;
in
{
imports = [ ../../../rofi-rbw ];
config = mkIf (cfg.enable && app == "rofi-rbw") {
programs = {
rbw = {
enable = true;
settings = {
# email = "you@example.tld"; # You have to set this in your config
pinentry = mkDefault pkgs.pinentry-gnome3;
lock_timeout = mkDefault 3600;
};
};
rofi-rbw = {
enable = true;
package = mkDefault pkgs.rofi-rbw-wayland;
settings = {
selector = mkDefault "bemenu";
selector-args = mkDefault "-i -l 20";
action = mkDefault "copy";
typing-key-delay = mkDefault 0;
};
};
librewolf = mkIf config.programs.librewolf.enable {
profiles.default.extensions.packages =
with inputs.nur.legacyPackages."${pkgs.stdenv.hostPlatform.system}".repos.rycee.firefox-addons; [
bitwarden
];
};
};
};
}

View file

@ -22,7 +22,7 @@ let
{
default = mkOption {
type = types.str;
inherit default;
default = default;
description = "The default application to use for the ${default}.";
};
bind = mkOption {
@ -38,8 +38,12 @@ let
};
# generate lists of all binds and window rules and remove empty strings
binds = filter (s: s != "") (builtins.concatLists (map (app: app.bind or [ "" ]) (attrValues apps)));
windowrules = filter (s: s != "") (builtins.concatLists (map (app: app.windowrule or [ "" ]) (attrValues apps)));
binds = filter (s: s != "") (
builtins.concatLists (map (app: app.bind or [ "" ]) (attrValues apps))
);
windowrules = filter (s: s != "") (
builtins.concatLists (map (app: app.windowrule or [ "" ]) (attrValues apps))
);
inherit (lib)
attrValues
@ -52,10 +56,8 @@ let
in
{
imports = [
./bitwarden
./bemenu
./dmenu-bluetooth
./dunst-toggle
./element-desktop
./feh
./kitty
@ -133,11 +135,6 @@ in
bind = [ "$mod SHIFT, m, exec, ${terminal} -T ${musicplayer} -e ${musicplayer}" ];
};
notifications = mkAppAttrs {
default = "dunst-toggle";
bind = [ "$mod, Backspace, exec, ${notifications}" ];
};
networksettings = mkAppAttrs {
default = "networkmanager_dmenu";
bind = [ "$mod SHIFT, n, exec, ${networksettings}" ];

View file

@ -1,18 +0,0 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.wayland.windowManager.hyprland;
app = cfg.applications.notifications.default;
inherit (lib) mkIf;
in
{
config = mkIf (cfg.enable && app == "dunst-toggle") {
home.packages = [ (import ./dunst-toggle.nix { inherit config pkgs; }) ];
};
}

View file

@ -1,11 +0,0 @@
{ config, pkgs, ... }:
let
dunst = "${pkgs.dunst}/bin/dunstctl";
pkill = "${pkgs.procps}/bin/pkill";
signal = "${toString config.programs.waybar.settings.mainBar."custom/notifications".signal}";
in
pkgs.writeShellScriptBin "dunst-toggle" ''
${dunst} set-paused toggle
${pkill} -RTMIN+${signal} waybar
''

View file

@ -9,7 +9,7 @@ let
cfg = config.wayland.windowManager.hyprland;
app = cfg.applications.rssreader.default;
reloadTime = "${toString config.programs.newsboat.reloadTime}";
newsboat-reload = import ./newsboat-reload.nix { inherit config pkgs; };
newsboat-reload = (import ./newsboat-reload.nix { inherit config pkgs; });
inherit (lib) mkIf;
in
@ -27,11 +27,9 @@ in
timers.newsboat-reload = {
Unit.Description = "Reload newsboat every ${reloadTime} minutes";
Timer = {
OnBootSec = "10sec";
OnUnitActiveSec = "${reloadTime}min";
Unit = "newsboat-reload.service";
};
Timer.OnBootSec = "10sec";
Timer.OnUnitActiveSec = "${reloadTime}min";
Timer.Unit = "newsboat-reload.service";
Install.WantedBy = [ "timers.target" ];
};

View file

@ -5,6 +5,6 @@ let
notify = "${pkgs.libnotify}/bin/notify-send";
signal = "${toString config.programs.waybar.settings.mainBar."custom/newsboat".signal}";
in
pkgs.writeShellScriptBin "newsboat-reload" ''
(pkgs.writeShellScriptBin "newsboat-reload" ''
${notify} -u low 'Newsboat' 'Reloading RSS feeds...' && ${newsboat} -x reload && ${notify} -u low 'Newsboat' 'RSS feeds reloaded.' && pkill -RTMIN+${signal} waybar
''
'')

View file

@ -14,7 +14,9 @@ in
{
config = mkIf (cfg.enable && app == "presentation-mode-bemenu") {
home.packages = [
(pkgs.writeShellScriptBin "presentation-mode-bemenu" (builtins.readFile ./presentation-mode-bemenu.sh))
(pkgs.writeShellScriptBin "presentation-mode-bemenu" (
builtins.readFile ./presentation-mode-bemenu.sh
))
];
};
}

View file

@ -1,6 +1,5 @@
#!/usr/bin/env bash
# Variables
mapfile -t DISPLAYS < <(hyprctl monitors | grep -E '^Monitor' | awk '{print $2}')
DISPLAYS=( $(hyprctl monitors | grep -E '^Monitor' | awk '{print $2}') )
EXTEND_RIGHT="Extend to right of main"
EXTEND_LEFT="Extend to left of main"
MIRROR="Mirror main"

View file

@ -5,7 +5,7 @@
let
screenshotDir = "${config.xdg.userDirs.pictures}/screenshots";
in
pkgs.writeShellScriptBin "screenshot" ''
(pkgs.writeShellScriptBin "screenshot" ''
mkdir -p ${screenshotDir}
${pkgs.hyprshot}/bin/hyprshot --mode $1 --output-folder ${screenshotDir} --filename screenshot_$(date +"%Y-%m-%d_%H-%M-%S").png
''
'')

View file

@ -27,7 +27,7 @@ in
wayland.windowManager.hyprland = {
settings = {
bind = binds;
bindm = import ./mouse.nix;
bindm = (import ./mouse.nix);
};
};
};

View file

@ -9,19 +9,17 @@ let
inherit (lib) mkForce;
in
{
home = {
pointerCursor = {
name = mkForce "Bibata-Original-Ice";
size = mkForce 24;
package = mkForce pkgs.bibata-cursors;
};
home.pointerCursor = {
name = mkForce "Bibata-Original-Ice";
size = mkForce 24;
package = mkForce pkgs.bibata-cursors;
};
packages = [ pkgs.hyprcursor ];
home.packages = [ pkgs.hyprcursor ];
sessionVariables = {
HYPRCURSOR_THEME = config.home.pointerCursor.name;
HYPRCURSOR_SIZE = toString config.home.pointerCursor.size;
};
home.sessionVariables = {
HYPRCURSOR_THEME = config.home.pointerCursor.name;
HYPRCURSOR_SIZE = toString config.home.pointerCursor.size;
};
# wayland.windowManager.hyprland.cursor.no_hardware_cursors = true;

View file

@ -78,6 +78,12 @@ in
# auto discover fonts in `home.packages`
fonts.fontconfig.enable = true;
# notifications
services.dunst = {
enable = mkDefault true;
waylandDisplay = config.home.sessionVariables.WAYLAND_DISPLAY;
};
# install some applications
home.packages = import ./packages.nix { inherit pkgs; }; # use programs.PACKAGE or services.SERVICE when possible
@ -88,19 +94,11 @@ in
fi
'';
# notifications
services = {
dunst = {
enable = mkDefault true;
waylandDisplay = config.home.sessionVariables.WAYLAND_DISPLAY;
};
udiskie = {
enable = mkDefault true;
tray = mkDefault "never";
};
network-manager-applet.enable = mkDefault true;
services.udiskie = {
enable = mkDefault true;
tray = mkDefault "never";
};
services.network-manager-applet.enable = mkDefault true;
};
}

View file

@ -3,30 +3,15 @@
let
cfg = config.wayland.windowManager.hyprland;
nonCenterFloatingClasses = [
"Gimp"
"steam"
"KiCad"
];
nonCenterFloatingClassesRegex = concatStringsSep "|" nonCenterFloatingClasses;
inherit (builtins) concatStringsSep toString;
inherit (builtins) toString;
inherit (lib) mkDefault;
in
{
# Do not add binds here. Use `./binds/default.nix` instead.
"$mod" = cfg.modifier;
exec-once = [
"dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP" # dbus package comes from NixOS module option `services.dbus.dbusPackage` [1]
"gnome-keyring-daemon --start --components=secrets" # gnome-keyring package comes from NixOS module `services.gnome.gnome-keyring` [1]
];
# 1: see Hyprland NixOS module
windowrule = [
# "float, class:^(${nonCenterFloatingClassesRegex})$"
"center, floating:1, class:^(?!.*(${nonCenterFloatingClassesRegex})).*$"
"center, floating:1, not class:^(Gimp)$, not class:^(steam)$"
"float, title:^(Open|Save) Files?$"
"noborder, onworkspace:w[t1]"
"bordersize ${toString cfg.settings.general.border_size}, floating:1"
@ -39,12 +24,6 @@ in
"noblur, class:^(xwaylandvideobridge)$"
];
gesture = [
"3, horizontal, workspace"
"3, up, dispatcher, exec, bemenu-run" # TODO: move to hyprland.applications
"4, swipe, move"
];
# Layouts
general.layout = mkDefault "master";
master = {

View file

@ -20,10 +20,8 @@ in
enable = mkDefault true;
createDirectories = mkDefault true;
};
portal = {
enable = mkDefault true;
extraPortals = [ portal ];
configPackages = [ portal ];
};
portal.enable = mkDefault true;
portal.extraPortals = [ portal ];
portal.configPackages = [ portal ];
};
}

View file

@ -19,13 +19,13 @@ let
in
{
urls = [ { template = engine.url; } ];
inherit (engine) icon;
icon = engine.icon;
updateInterval = if (isUrl engine.icon) then every_day else null;
definedAliases = optional (engine ? alias) engine.alias;
};
transformedEngines = mapAttrs' (name: engine: {
inherit name;
name = name;
value = transformEngine engine;
}) engines;
@ -79,7 +79,7 @@ in
profiles.default.search.engines = mapAttrs (_: name: transformedEngines.${name}) (
listToAttrs (
map (name: {
inherit name;
name = name;
value = name;
}) cfg.searchEngines
)

View file

@ -1,71 +1,79 @@
[
# cursor navigation
{
options.desc = "scroll down, recenter";
# scroll down, recenter
key = "<C-d>";
action = "<C-d>zz";
mode = "n";
}
{
options.desc = "scroll up, recenter";
# scroll up, recenter
key = "<C-u>";
action = "<C-u>zz";
mode = "n";
}
# searching
{
options.desc = "center cursor after search next";
# center cursor after search next
key = "n";
action = "nzzzv";
mode = "n";
}
{
options.desc = "center cursor after search previous";
# center cursor after search previous
key = "N";
action = "Nzzzv";
mode = "n";
}
{
options.desc = "ex command";
# ex command
key = "<leader>pv";
action = "<cmd>Ex<CR>";
mode = "n";
}
# search and replace
{
options.desc = "search and replace word under cursor";
# search and replace word under cursor
key = "<leader>s";
action = ":%s/<C-r><C-w>/<C-r><C-w>/gI<Left><Left><Left>";
mode = "n";
}
# search and replace selected text
{
key = "<leader>s";
action = "y:%s/<C-r>0/<C-r>0/gI<Left><Left><Left>";
mode = "v";
}
# clipboard operations
{
options.desc = "copy to system clipboard in visual mode";
# copy to system clipboard in visual mode
key = "<C-c>";
action = ''"+y '';
mode = "v";
}
{
options.desc = "paste from system clipboard in visual mode";
# paste from system clipboard in visual mode
key = "<C-v>";
action = ''"+p '';
mode = "v";
}
{
options.desc = "yank to system clipboard";
# yank to system clipboard
key = "<leader>Y";
action = "+Y";
mode = "n";
}
{
options.desc = "replace selected text with clipboard content";
# replace selected text with clipboard content
key = "<leader>p";
action = "_dP";
mode = "x";
}
{
options.desc = "delete without copying to clipboard";
# delete without copying to clipboard
key = "<leader>d";
action = "_d";
mode = [
@ -73,184 +81,267 @@
"v"
];
}
# line operations
{
options.desc = "move lines down in visual mode";
# move lines down in visual mode
key = "J";
action = ":m '>+1<CR>gv=gv";
mode = "v";
}
{
options.desc = "move lines up in visual mode";
# move lines up in visual mode
key = "K";
action = ":m '<-2<CR>gv=gv";
mode = "v";
}
{
options.desc = "join lines";
# join lines
key = "J";
action = "mzJ`z";
mode = "n";
}
# quickfix
{
options.desc = "Run make command";
# Run make command
key = "<leader>m";
action = "<cmd>:make<CR>";
mode = "n";
}
{
options.desc = "previous quickfix item";
# previous quickfix item
key = "<C-A-J>";
action = "<cmd>cprev<CR>zz";
mode = "n";
}
{
options.desc = "next quickfix item";
# next quickfix item
key = "<C-A-K>";
action = "<cmd>cnext<CR>zz";
mode = "n";
}
# location list navigation
{
options.desc = "previous location list item";
# previous location list item
key = "<leader>j";
action = "<cmd>lprev<CR>zz";
mode = "n";
}
{
options.desc = "next location list item";
# next location list item
key = "<leader>k";
action = "<cmd>lnext<CR>zz";
mode = "n";
}
# disabling keys
{
options.desc = "disable the 'Q' key";
# disable the 'Q' key
key = "Q";
action = "<nop>";
mode = "n";
}
# text selection
{
options.desc = "select whole buffer";
# select whole buffer
key = "<C-a>";
action = "ggVG";
mode = "n";
}
# window operations
{
options.desc = "focus next window";
# focus next window
key = "<C-j>";
action = ":wincmd W<CR>";
options = {
noremap = true;
silent = true;
};
mode = "n";
}
{
options.desc = "focus next window";
key = "<Tab>";
action = ":wincmd W<CR>";
mode = "n";
}
{
options.desc = "focus previous window";
# focus previous window
key = "<C-k>";
action = ":wincmd w<CR>";
options = {
noremap = true;
silent = true;
};
mode = "n";
}
# window size adjustments
{
options.desc = "focus previous window";
key = "<S-Tab>";
action = ":wincmd w<CR>";
mode = "n";
}
{
options.desc = "increase window width";
# increase window width
key = "<C-l>";
action = ":vertical resize +5<CR>";
options = {
noremap = true;
silent = true;
};
mode = "n";
}
{
options.desc = "decrease window width";
# decrease window width
key = "<C-h>";
action = ":vertical resize -5<CR>";
options = {
noremap = true;
silent = true;
};
mode = "n";
}
# window closing and opening
{
options.desc = "close current window";
# close current window
key = "<leader-S>c";
action = ":q<CR>";
options = {
noremap = true;
silent = true;
};
mode = "n";
}
{
options.desc = "new vertical split at $HOME";
# new vertical split at $HOME
key = "<leader>n";
action = ":vsp $HOME<CR>";
options = {
noremap = true;
silent = true;
};
mode = "n";
}
# window split orientation toggling
{
options.desc = "toggle split orientation";
# toggle split orientation
key = "<leader>t";
action = ":wincmd T<CR>";
options = {
noremap = true;
silent = true;
};
mode = "n";
}
# spell checking
{
options.desc = "toggle spell checking";
# toggle spell checking
key = "<leader>ss";
action = ":setlocal spell!<CR>";
options = {
noremap = true;
silent = true;
};
mode = "n";
}
{
options.desc = "switch to english spell checking";
# switch to english spell checking
key = "<leader>se";
action = ":setlocal spelllang=en_us<CR>";
options = {
noremap = true;
silent = true;
};
mode = "n";
}
{
options.desc = "switch to german spell checking";
# switch to german spell checking
key = "<leader>sg";
action = ":setlocal spelllang=de_20<CR>";
options = {
noremap = true;
silent = true;
};
mode = "n";
}
{
options.desc = "move to next misspelling";
# move to next misspelling
key = "]s";
action = "]szz";
options = {
noremap = true;
silent = true;
};
mode = "n";
}
{
options.desc = "move to previous misspelling";
# move to previous misspelling
key = "[s";
action = "[szz";
options = {
noremap = true;
silent = true;
};
mode = "n";
}
{
options.desc = "correction suggestions for a misspelled word";
# correction suggestions for a misspelled word
key = "z=";
action = "z=";
options = {
noremap = true;
silent = true;
};
mode = "n";
}
{
options.desc = "adding words to the dictionary";
# adding words to the dictionary
key = "zg";
action = "zg";
options = {
noremap = true;
silent = true;
};
mode = "n";
}
# buffer navigation
{
options.desc = "next buffer";
# next buffer
key = "<C-S-J>";
action = ":bnext<CR>";
options = {
noremap = true;
silent = true;
};
mode = "n";
}
{
options.desc = "previous buffer";
# previous buffer
key = "<C-S-K>";
action = ":bprevious<CR>";
options = {
noremap = true;
silent = true;
};
mode = "n";
}
{
options.desc = "close current buffer";
# close current buffer
key = "<leader>bd";
action = ":bdelete<CR>";
options = {
noremap = true;
silent = true;
};
mode = "n";
}
{
options.desc = "apply code action";
# apply code action
key = "<leader>ca";
action = ":lua vim.lsp.buf.code_action()<CR>";
options = {
noremap = true;
silent = true;
};
mode = "n";
}
]

View file

@ -3,17 +3,14 @@
{
imports = [
./cmp.nix
./diffview.nix
./lsp.nix
./gitsigns.nix
./lualine.nix
./telescope.nix
./treesitter.nix
# ./treesitter.nix # HOTFIX: does not build
./trouble.nix
];
config.programs.nixvim.plugins = {
which-key.enable = lib.mkDefault true;
markdown-preview.enable = lib.mkDefault true;
# warning: Nixvim: `plugins.web-devicons` was enabled automatically because the following plugins are enabled. This behaviour is deprecated. Please explicitly define `plugins.web-devicons.enable`
web-devicons.enable = true;

View file

@ -1,59 +0,0 @@
{ config, lib, ... }:
let
cfg = config.programs.nixvim;
plugin = cfg.plugins.diffview;
inherit (lib) mkDefault mkIf;
in
{
config = {
programs.nixvim = {
plugins.diffview = {
enable = mkDefault true;
};
# highlight = mkIf plugin.enable {
# DiffAdd = {
# bg = "#2d4a2d";
# fg = "NONE";
# };
# DiffDelete = {
# bg = "#4a2d2d";
# fg = "NONE";
# };
# DiffChange = {
# bg = "#2d3a4a";
# fg = "NONE";
# };
# DiffText = {
# bg = "#1a5a1a";
# fg = "NONE";
# };
# };
keymaps = mkIf plugin.enable [
{
mode = "n";
key = "<leader>gd";
action.__raw = ''
function()
local lib = require("diffview.lib")
local view = lib.get_current_view()
if view then
vim.cmd("DiffviewClose")
else
vim.cmd("DiffviewOpen")
end
end
'';
options = {
noremap = true;
silent = true;
desc = "toggle git diff";
};
}
];
};
};
}

View file

@ -1,104 +0,0 @@
{ config, lib, ... }:
let
cfg = config.programs.nixvim;
plugin = cfg.plugins.gitsigns;
inherit (lib) mkDefault mkIf;
in
{
config = {
programs.nixvim = {
plugins.gitsigns = {
enable = mkDefault true;
settings = {
current_line_blame = mkDefault false;
current_line_blame_opts = {
virt_text = mkDefault true;
virt_text_pos = mkDefault "eol";
};
signcolumn = mkDefault true;
signs = {
add.text = mkDefault "+";
change.text = mkDefault "";
changedelete.text = mkDefault "~";
delete.text = mkDefault "-";
topdelete.text = mkDefault "-";
untracked.text = mkDefault "?";
};
watch_gitdir.follow_files = mkDefault true;
};
};
keymaps = mkIf plugin.enable [
{
mode = "n";
key = "<leader>hs";
action.__raw = "function() require('gitsigns').stage_hunk() end";
options = {
noremap = true;
silent = true;
desc = "stage hunk";
};
}
{
mode = "v";
key = "<leader>hs";
action.__raw = ''
function()
require('gitsigns').stage_hunk({vim.fn.line('.'), vim.fn.line('v')})
end
'';
options = {
noremap = true;
silent = true;
desc = "stage selected hunks";
};
}
{
mode = "n";
key = "<leader>hu";
action.__raw = "function() require('gitsigns').undo_stage_hunk() end";
options = {
noremap = true;
silent = true;
desc = "undo stage hunk";
};
}
{
mode = "n";
key = "<leader>hS";
action.__raw = "function() require('gitsigns').stage_buffer() end";
options = {
noremap = true;
silent = true;
desc = "stage buffer";
};
}
{
mode = "n";
key = "<leader>hr";
action.__raw = "function() require('gitsigns').reset_hunk() end";
options = {
noremap = true;
silent = true;
desc = "reset hunk";
};
}
{
mode = "v";
key = "<leader>hr";
action.__raw = ''
function()
require('gitsigns').reset_hunk({vim.fn.line('.'), vim.fn.line('v')})
end
'';
options = {
noremap = true;
silent = true;
desc = "reset selected hunks";
};
}
];
};
};
}

View file

@ -16,31 +16,6 @@ in
programs.nixvim = {
plugins.telescope = {
enable = mkDefault true;
settings = {
defaults = {
vimgrep_arguments = [
"rg"
"--color=never"
"--no-heading"
"--with-filename"
"--line-number"
"--column"
"--smart-case"
"--hidden"
];
file_ignore_patterns = [
"^.git/"
"^.direnv/"
"^.cache/"
"^node_modules/"
];
};
pickers = {
find_files = {
hidden = true;
};
};
};
extensions = {
file-browser.enable = mkDefault true;
fzf-native.enable = mkDefault true;

View file

@ -16,19 +16,17 @@ in
{
config = {
programs.nixvim = {
plugins = {
treesitter = {
enable = mkDefault true;
nixvimInjections = mkDefault true;
settings = {
folding.enable = mkDefault true;
highlight.enable = mkDefault true;
indent.enable = mkDefault true;
};
plugins.treesitter = {
enable = mkDefault true;
nixvimInjections = mkDefault true;
settings = {
folding.enable = mkDefault true;
highlight.enable = mkDefault true;
indent.enable = mkDefault true;
};
treesitter-context = mkIf plugin.enable { enable = mkDefault true; };
treesitter-textobjects = mkIf plugin.enable { enable = mkDefault true; };
};
plugins.treesitter-context = mkIf plugin.enable { enable = mkDefault true; };
plugins.treesitter-textobjects = mkIf plugin.enable { enable = mkDefault true; };
};
# Fix for: ERROR `cc` executable not found.

View file

@ -2,7 +2,7 @@
let
spellDir = config.xdg.dataHome + "/nvim/site/spell";
baseUrl = "https://vim.ftp.fu-berlin.de/runtime/spell";
baseUrl = "http://ftp.de.vim.org/runtime/spell";
in
{
home.file = {
@ -12,7 +12,7 @@ in
url = baseUrl + "/de.utf-8.spl";
sha256 = "sha256-c8cQfqM5hWzb6SHeuSpFk5xN5uucByYdobndGfaDo9E=";
};
target = spellDir + "/de.utf-8.spl";
target = spellDir + "/de.utf8.spl";
};
de-sug = {
enable = true;
@ -20,7 +20,7 @@ in
url = baseUrl + "/de.utf-8.sug";
sha256 = "sha256-E9Ds+Shj2J72DNSopesqWhOg6Pm6jRxqvkerqFcUqUg=";
};
target = spellDir + "/de.utf-8.sug";
target = spellDir + "/de.utf8.sug";
};
};
}

View file

@ -9,7 +9,7 @@
let
cfg = config.programs.passwordManager;
passmenuScript = pkgs.writeShellScriptBin "passmenu-bemenu" (builtins.readFile ./passmenu); # TODO: override original passmenu script coming from pass itself
inherit (pkgs) passff-host;
passff-host = pkgs.passff-host;
inherit (lib)
mkDefault

View file

@ -17,7 +17,6 @@ let
"moonfly"
"nord"
"oxocarbon"
"generate-from-image"
];
# schemes names in `pkgs.base16-schemes` that need a suffix
needsSuffix = [
@ -29,7 +28,13 @@ let
"moonfly"
"oxocarbon"
];
schemeName = if builtins.elem cfg.scheme needsSuffix then "${cfg.scheme}-${cfg.polarity}" else cfg.scheme;
schemeName =
if builtins.elem cfg.scheme needsSuffix then "${cfg.scheme}-${cfg.polarity}" else cfg.scheme;
scheme =
if builtins.elem cfg.scheme customSchemes then
./schemes/${schemeName}.yaml
else
"${pkgs.base16-schemes}/share/themes/${schemeName}.yaml";
inherit (lib)
mkDefault
@ -51,8 +56,6 @@ in
description = ''
Base16 color scheme name. Available options are:
${toString validSchemes}
"generate-from-image" generates a color scheme from `stylix.image`
'';
};
};
@ -67,12 +70,7 @@ in
stylix = {
autoEnable = mkDefault true;
base16Scheme = mkIf (cfg.scheme != "generate-from-image") (
if builtins.elem cfg.scheme customSchemes then
./schemes/${schemeName}.yaml
else
"${pkgs.base16-schemes}/share/themes/${schemeName}.yaml"
);
base16Scheme = scheme;
fonts = {
monospace = mkDefault {
package = pkgs.hack-font;

View file

@ -1,4 +1,3 @@
---
system: "base16"
name: "Moonfly"
description: "A dark theme inspired by the Moonfly color scheme."

View file

@ -1,4 +1,3 @@
---
system: "base16"
name: "Oxocarbon"
description: "A dark theme inspired by the Oxocarbon Dark color scheme."

View file

@ -4,13 +4,11 @@ let
cfg = config.stylix;
target = cfg.targets.nixvim;
inherit (lib) mkIf optionalAttrs;
inherit (lib) mkIf;
in
{
config = mkIf cfg.enable {
stylix.targets.nixvim.enable = false;
programs.nixvim.colorschemes = optionalAttrs (cfg.scheme != "generate-from-image") {
"${cfg.scheme}".enable = !target.enable;
};
programs.nixvim.colorschemes."${cfg.scheme}".enable = !target.enable;
};
}

View file

@ -71,11 +71,11 @@ in
#workspaces {
color: ${colors.base05};
background: ${colors.base00};
}
#workspaces button {
padding: ${halfgaps}px;
color: ${colors.base05};
}
#workspaces button.active {

View file

@ -56,7 +56,6 @@ let
};
# Add your custom modules here
"custom/notifications" = import ./modules/notifications.nix { inherit lib pkgs; };
"custom/newsboat" = import ./modules/newsboat.nix { inherit lib pkgs; };
"pulseaudio#input" = import ./modules/pulseaudio/input.nix { inherit lib pkgs; };
"pulseaudio#output" = import ./modules/pulseaudio/output.nix { inherit lib pkgs; };
@ -96,13 +95,11 @@ in
"disk"
"pulseaudio#input"
"pulseaudio#output"
"custom/notifications"
"tray"
];
inherit
"custom/newsboat"
"custom/notifications"
"hyprland/language"
"hyprland/workspaces"
"pulseaudio#input"
@ -132,7 +129,5 @@ in
};
};
};
services.batsignal.enable = builtins.elem "battery" cfg.settings.mainBar.modules-right;
};
}

View file

@ -10,13 +10,13 @@ let
let
newsboat = "${pkgs.newsboat}/bin/newsboat";
in
pkgs.writeShellScriptBin "newsboat-print-unread" ''
(pkgs.writeShellScriptBin "newsboat-print-unread" ''
UNREAD=$(${newsboat} -x print-unread | awk '{print $1}')
if [[ $UNREAD -gt 0 ]]; then
printf " %i" "$UNREAD"
fi
'';
'');
inherit (lib) mkDefault;
in

View file

@ -1,27 +0,0 @@
{ pkgs, lib, ... }:
let
inherit (lib) mkDefault;
dunst = "${pkgs.dunst}/bin/dunstctl";
in
{
format = mkDefault "{icon}";
format-icons = {
unmuted = mkDefault "";
muted = mkDefault "";
};
return-type = mkDefault "json";
exec = mkDefault (
pkgs.writeShellScript "notifications" ''
is_paused=$(${dunst} is-paused)
if [ "$is_paused" = "true" ]; then
echo '{"alt": "muted", "class": "muted"}'
else
echo '{"alt": "unmuted", "class": "unmuted"}'
fi
''
);
on-click = mkDefault "dunst-toggle";
interval = mkDefault "once";
signal = mkDefault 12;
}

View file

@ -1,4 +1,3 @@
#!/usr/bin/env bash
TIMER_FILE="/tmp/timer" # file to store the current time
SIGNAL=11 # signal number to send to status bar
STATUS_BAR="waybar" # Support for more status bars?
@ -17,7 +16,7 @@ start_timer() {
notify-send "Timer Started" "Your countdown timer has been started."
trap "exit" INT TERM
trap 'rm -f -- "$TIMER_FILE"' EXIT
trap "rm -f -- '$TIMER_FILE'" EXIT
while [ $total_seconds -gt 0 ]; do
hours=$(( total_seconds / 3600 ))
@ -63,7 +62,7 @@ if [ "$1" = "start" ]; then
MINUTES=${MINUTES:-0}
SECONDS=${SECONDS:-0}
start_timer "$HOURS" "$MINUTES" "$SECONDS"
start_timer $HOURS $MINUTES $SECONDS
elif [ "$1" = "stop" ]; then
notify-send "Timer Stopped" "Your countdown timer has been stopped."

View file

@ -6,19 +6,19 @@
environment.systemPackages = with pkgs; [
lact
nvtopPackages.amd
rocmPackages.clr.icd
rocmPackages.hipcc
rocmPackages.miopen
rocmPackages.rocm-runtime
rocmPackages.rocm-smi
rocmPackages.rocminfo
];
# environment.variables.ROC_ENABLE_PRE_VEGA = "1"; # for Polaris
hardware.amdgpu.opencl.enable = true;
hardware.graphics.extraPackages = with pkgs; [ rocmPackages.clr.icd ];
systemd.packages = with pkgs; [ lact ];
systemd.services.lactd.wantedBy = [ "multi-user.target" ];
}

View file

@ -4,16 +4,10 @@ let
inherit (lib) mkDefault;
in
{
hardware = {
bluetooth = {
enable = mkDefault true;
powerOnBoot = mkDefault false;
settings.General = {
Enable = mkDefault "Source,Sink,Media,Socket";
Experimental = mkDefault true;
};
};
};
hardware.bluetooth.enable = mkDefault true;
hardware.bluetooth.powerOnBoot = mkDefault false;
hardware.bluetooth.settings.General.Enable = mkDefault "Source,Sink,Media,Socket";
hardware.bluetooth.settings.General.Experimental = mkDefault true;
environment.systemPackages = with pkgs; [
blueman

View file

@ -84,6 +84,8 @@ in
) { } cfg.remotes;
# Ensure that all cifs-mount services are started with the graphical session
systemd.user.targets.graphical-session.wants = map (remote: "cifs-mount-${remote.shareName}.service") cfg.remotes;
systemd.user.targets.graphical-session.wants = map (
remote: "cifs-mount-${remote.shareName}.service"
) cfg.remotes;
};
}

View file

@ -1,20 +0,0 @@
{ lib, pkgs, ... }:
{
boot = {
# fix CVE-2026-31431
kernelPackages = lib.mkIf (lib.versionOlder pkgs.linux.version "6.18.22") (lib.mkDefault pkgs.linuxPackages_6_18);
# fix CVE-2026-43500
extraModprobeConfig = ''
install esp4 ${pkgs.coreutils}/bin/false
install esp6 ${pkgs.coreutils}/bin/false
install rxrpc ${pkgs.coreutils}/bin/false
'';
blacklistedKernelModules = [
"esp4"
"esp6"
"rxrpc"
];
};
}

View file

@ -1,6 +1,5 @@
{
imports = [
./boot.nix
./environment.nix
./htop.nix
./nationalization.nix

View file

@ -9,59 +9,55 @@ let
inherit (lib) mkDefault optionals;
in
{
environment = {
systemPackages =
with pkgs;
[
cryptsetup
curl
dig
dnsutils
fzf
gptfdisk
iproute2
jq
lm_sensors
lsof
netcat-openbsd
nettools
nixos-container
nmap
nurl
p7zip
pciutils
psmisc
rclone
rsync
tcpdump
tmux
tree
unzip
usbutils
wget
xxd
zip
environment.systemPackages =
with pkgs;
[
cryptsetup
curl
dig
dnsutils
fzf
gptfdisk
iproute2
jq
lm_sensors
lsof
netcat-openbsd
nettools
nixos-container
nmap
nurl
p7zip
pciutils
psmisc
rclone
rsync
tcpdump
tmux
tree
unzip
usbutils
wget
xxd
zip
(callPackage ../../../apps/rebuild { })
]
++ optionals (pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform) [
pkgs.kitty.terminfo
];
(callPackage ../../../apps/rebuild { })
]
++ optionals (pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform) [
pkgs.kitty.terminfo
];
shellAliases = {
l = "ls -lh";
ll = "ls -lAh";
ports = "ss -tulpn";
publicip = "curl ifconfig.me/all";
sudo = "sudo "; # make aliases work with `sudo`
};
# saves one instance of nixpkgs.
ldso32 = null;
environment.shellAliases = {
l = "ls -lh";
ll = "ls -lAh";
ports = "ss -tulpn";
publicip = "curl ifconfig.me/all";
sudo = "sudo "; # make aliases work with `sudo`
};
boot = {
tmp.cleanOnBoot = mkDefault true;
initrd.systemd.enable = mkDefault (!config.boot.swraid.enable && !config.boot.isContainer);
};
# saves one instance of nixpkgs.
environment.ldso32 = null;
boot.tmp.cleanOnBoot = mkDefault true;
boot.initrd.systemd.enable = mkDefault (!config.boot.swraid.enable && !config.boot.isContainer);
}

View file

@ -2,13 +2,16 @@
# avoid TOFU MITM
programs.ssh.knownHosts = {
"github.com".hostNames = [ "github.com" ];
"github.com".publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl";
"github.com".publicKey =
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl";
"gitlab.com".hostNames = [ "gitlab.com" ];
"gitlab.com".publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjquxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf";
"gitlab.com".publicKey =
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjquxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf";
"git.sr.ht".hostNames = [ "git.sr.ht" ];
"git.sr.ht".publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMZvRd4EtM7R+IHVMWmDkVU3VLQTSwQDSAvW0t2Tkj60";
"git.sr.ht".publicKey =
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMZvRd4EtM7R+IHVMWmDkVU3VLQTSwQDSAvW0t2Tkj60";
};
# TODO: add synix
}

View file

@ -42,8 +42,12 @@ in
static-auth-secret-file = mkIf cfg.sops config.sops.secrets."coturn/static-auth-secret".path;
realm = mkDefault "turn.${config.networking.domain}";
cert = mkIf (!cfg.no-tls && cfg.sops) "${config.security.acme.certs.${cfg.realm}.directory}/full.pem";
pkey = mkIf (!cfg.no-tls && cfg.sops) "${config.security.acme.certs.${cfg.realm}.directory}/key.pem";
cert =
mkIf (!cfg.no-tls && cfg.sops)
"${config.security.acme.certs.${cfg.realm}.directory}/full.pem";
pkey =
mkIf (!cfg.no-tls && cfg.sops)
"${config.security.acme.certs.${cfg.realm}.directory}/key.pem";
extraConfig = ''
# ban private IP ranges

View file

@ -14,7 +14,6 @@
i2pd = import ./i2pd;
jellyfin = import ./jellyfin;
jirafeau = import ./jirafeau;
librechat-oci = import ./librechat-oci;
mailserver = import ./mailserver;
matrix-synapse = import ./matrix-synapse;
maubot = import ./maubot;
@ -22,7 +21,6 @@
miniflux = import ./miniflux;
nginx = import ./nginx;
normalUsers = import ./normalUsers;
nostr-relay = import ./nostr-relay;
nvidia = import ./nvidia;
ollama = import ./ollama;
open-webui-oci = import ./open-webui-oci;
@ -35,4 +33,5 @@
virtualisation = import ./virtualisation;
webPage = import ./webPage;
windows-oci = import ./windows-oci;
zfs = import ./zfs;
}

View file

@ -33,13 +33,11 @@ in
fonts.fontconfig.enable = mkDefault false;
xdg = {
autostart.enable = mkDefault false;
icons.enable = mkDefault false;
menus.enable = mkDefault false;
mime.enable = mkDefault false;
sounds.enable = mkDefault false;
};
xdg.autostart.enable = mkDefault false;
xdg.icons.enable = mkDefault false;
xdg.menus.enable = mkDefault false;
xdg.mime.enable = mkDefault false;
xdg.sounds.enable = mkDefault false;
programs.git.package = mkDefault pkgs.gitMinimal;
@ -50,25 +48,22 @@ in
viAlias = mkDefault true;
};
systemd = {
# emergency mode is useless on headless machines
enableEmergencyMode = false;
sleep.extraConfig = ''
AllowSuspend=no
AllowHibernation=no
'';
# force reboots
settings.Manager = {
RuntimeWatchdogSec = mkDefault "15s";
RebootWatchdogSec = mkDefault "30s";
KExecWatchdogSec = mkDefault "1m";
};
};
# emergency mode is useless on headless machines
systemd.enableEmergencyMode = false;
boot.initrd.systemd.suppressedUnits = mkIf config.systemd.enableEmergencyMode [
"emergency.service"
"emergency.target"
];
systemd.sleep.extraConfig = ''
AllowSuspend=no
AllowHibernation=no
'';
# force reboots
systemd.settings.Manager = {
RuntimeWatchdogSec = mkDefault "15s";
RebootWatchdogSec = mkDefault "30s";
KExecWatchdogSec = mkDefault "1m";
};
}

View file

@ -4,9 +4,7 @@ let
inherit (lib) mkDefault;
in
{
services = {
qemuGuest.enable = mkDefault true;
spice-vdagentd.enable = mkDefault true;
spice-webdavd.enable = mkDefault true;
};
services.qemuGuest.enable = mkDefault true;
services.spice-vdagentd.enable = mkDefault true;
services.spice-webdavd.enable = mkDefault true;
}

View file

@ -2,9 +2,9 @@
let
cfg = config.services.ftp-webserver;
inherit (config.networking) domain;
domain = config.networking.domain;
fqdn = if (cfg.subdomain != "") then "${cfg.subdomain}.${domain}" else domain;
inherit (config.services) nginx;
nginx = config.services.nginx;
inherit (lib)
mkEnableOption
@ -35,7 +35,7 @@ in
config = mkIf cfg.enable {
services.nginx.virtualHosts."${fqdn}" = {
inherit (cfg) root;
root = cfg.root;
locations."/" = {
extraConfig = ''
autoindex on;
@ -43,7 +43,7 @@ in
autoindex_localtime on;
'';
};
inherit (cfg) forceSSL;
forceSSL = cfg.forceSSL;
enableACME = cfg.forceSSL;
sslCertificate = mkIf cfg.forceSSL "${config.security.acme.certs."${fqdn}".directory}/cert.pem";
sslCertificateKey = mkIf cfg.forceSSL "${config.security.acme.certs."${fqdn}".directory}/key.pem";

View file

@ -7,10 +7,10 @@
let
cfg = config.services.headplane;
inherit (config.networking) domain;
inherit (cfg.reverseProxy) subdomain;
domain = config.networking.domain;
subdomain = cfg.reverseProxy.subdomain;
fqdn = if (cfg.reverseProxy.enable && subdomain != "") then "${subdomain}.${domain}" else domain;
inherit (config.services) headscale;
headscale = config.services.headscale;
inherit (lib)
mkDefault
@ -45,17 +45,17 @@ in
url = "http://127.0.0.1:${toString headscale.port}";
public_url = headscale.settings.server_url;
config_path = "/etc/headscale/config.yaml";
api_key_path = config.sops.secrets."headplane/api_key".path;
};
integration.agent = {
enabled = mkDefault true;
pre_authkey_path = config.sops.secrets."headplane/agent_pre_authkey".path;
};
};
};
services.nginx.virtualHosts = mkIf cfg.reverseProxy.enable {
"${fqdn}" = mkVirtualHost {
inherit (cfg.settings.server) port;
port = cfg.settings.server.port;
ssl = cfg.reverseProxy.forceSSL;
};
};
@ -63,14 +63,14 @@ in
sops.secrets =
let
owner = headscale.user;
inherit (headscale) group;
group = headscale.group;
mode = "0400";
in
{
"headplane/cookie_secret" = {
inherit owner group mode;
};
"headplane/api_key" = {
"headplane/agent_pre_authkey" = {
inherit owner group mode;
};
};

View file

@ -6,8 +6,8 @@
let
cfg = config.services.headscale;
inherit (config.networking) domain;
inherit (cfg.reverseProxy) subdomain;
domain = config.networking.domain;
subdomain = cfg.reverseProxy.subdomain;
fqdn = if (cfg.reverseProxy.enable && subdomain != "") then "${subdomain}.${domain}" else domain;
acl = "headscale/acl.hujson";

View file

@ -8,9 +8,20 @@ in
programs.dconf.enable = true; # fixes nixvim hm module
services = {
flatpak.enable = true;
gnome.gnome-keyring.enable = true;
udisks2.enable = mkDefault true;
xdg.portal = {
enable = true;
extraPortals = with pkgs; [
xdg-desktop-portal-gtk
];
};
services.gnome.gnome-keyring.enable = true;
security.pam.services = {
login = {
enableGnomeKeyring = true;
};
hyprlock = { };
};
services.udisks2.enable = mkDefault true;
}

View file

@ -7,8 +7,8 @@
let
cfg = config.services.jellyfin;
inherit (config.networking) domain;
inherit (cfg.reverseProxy) subdomain;
domain = config.networking.domain;
subdomain = cfg.reverseProxy.subdomain;
fqdn = if (cfg.reverseProxy.enable && subdomain != "") then "${subdomain}.${domain}" else domain;
inherit (lib)
@ -48,7 +48,9 @@ in
];
systemd.tmpfiles.rules =
(map (library: "d ${cfg.dataDir}/libraries/${library} 0770 ${cfg.user} ${cfg.group} -") cfg.libraries)
(map (
library: "d ${cfg.dataDir}/libraries/${library} 0770 ${cfg.user} ${cfg.group} -"
) cfg.libraries)
++ [
"z ${cfg.dataDir} 0770 ${cfg.user} ${cfg.group} -"
"Z ${cfg.dataDir}/libraries 0770 ${cfg.user} ${cfg.group} -"

View file

@ -2,8 +2,8 @@
let
cfg = config.services.jirafeau;
inherit (config.networking) domain;
inherit (cfg.reverseProxy) subdomain;
domain = config.networking.domain;
subdomain = cfg.reverseProxy.subdomain;
fqdn = if (cfg.reverseProxy.enable && subdomain != "") then "${subdomain}.${domain}" else domain;
inherit (lib)

View file

@ -1,535 +0,0 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.librechat-oci;
defaultImages = {
librechat = pkgs.dockerTools.pullImage {
imageName = "ghcr.io/danny-avila/librechat";
imageDigest = "sha256:a46254938507971e0d4f7ed3f9d116bd9b118f4810b5b75eb716baf575645068";
hash = "sha256-zevUN6vrs3hymwCGFmk/YXlUzYjN37H+EO5aLxYchyc=";
finalImageName = "ghcr.io/danny-avila/librechat";
finalImageTag = "v0.8.5";
};
meilisearch = pkgs.dockerTools.pullImage {
imageName = "getmeili/meilisearch";
imageDigest = "sha256:b839a48d008d4e67e57f78dcff5b21d5e8b8fa066bacd11f97824d6307abb0dd";
hash = "sha256-+hypvjFDSdnnWAO4tARTnjNd/6HlF6pMg1L6UUffdYM=";
finalImageName = "getmeili/meilisearch";
finalImageTag = "v1.44.0";
};
mongodb = pkgs.dockerTools.pullImage {
imageName = "mongo";
imageDigest = "sha256:098862b1339f031900ca66cf8fef799e616d6324fa41b9a263f2ec899552c1ef";
hash = "sha256-XuWnvcqAAkAGshdQtnngKOOJP2Bd33FXTbGHTRX3nUc=";
finalImageName = "mongo";
finalImageTag = "8.0.20";
};
vectordb = pkgs.dockerTools.pullImage {
imageName = "pgvector/pgvector";
imageDigest = "sha256:8809cfffff0082cf260c9ac752f1dd1afc77f6f0a55c4e6411321e78efc3d9a5";
hash = "sha256-rc6gQLMzv8UOZVLmWKGUESNIo+iPf5DR7T79AmbzWc4=";
finalImageName = "pgvector/pgvector";
finalImageTag = "0.8.0-pg15-trixie";
};
ragApi = pkgs.dockerTools.pullImage {
imageName = "registry.librechat.ai/danny-avila/librechat-rag-api-dev-lite";
imageDigest = "sha256:6dfb6832661ff9c26fa329c823ce266059e33567670a763e9ecb9b566b8daa68";
hash = "sha256-k8pkEgbqT4NU2+2ZjdRFlfFvMUk/1p+pkysbELh95pM=";
finalImageName = "registry.librechat.ai/danny-avila/librechat-rag-api-dev-lite";
finalImageTag = "latest";
};
};
defaultEnv = {
# Server
HOST = "0.0.0.0";
PORT = toString cfg.port;
NO_INDEX = "true";
TRUST_PROXY = "1";
# Logging
CONSOLE_JSON = "false";
DEBUG_LOGGING = "true";
DEBUG_CONSOLE = "false";
AGENT_DEBUG_LOGGING = "false";
DEBUG_OPENAI = "false";
# Node
NODE_MAX_OLD_SPACE_SIZE = "6144";
# Search
SEARCH = "true";
MEILI_NO_ANALYTICS = "true";
# Moderation
OPENAI_MODERATION = "false";
BAN_VIOLATIONS = "true";
BAN_DURATION = "1000 * 60 * 60 * 2";
BAN_INTERVAL = "20";
LOGIN_VIOLATION_SCORE = "1";
REGISTRATION_VIOLATION_SCORE = "1";
CONCURRENT_VIOLATION_SCORE = "1";
MESSAGE_VIOLATION_SCORE = "1";
NON_BROWSER_VIOLATION_SCORE = "20";
TTS_VIOLATION_SCORE = "0";
STT_VIOLATION_SCORE = "0";
FORK_VIOLATION_SCORE = "0";
IMPORT_VIOLATION_SCORE = "0";
FILE_UPLOAD_VIOLATION_SCORE = "0";
LOGIN_MAX = "7";
LOGIN_WINDOW = "5";
REGISTER_MAX = "5";
REGISTER_WINDOW = "60";
LIMIT_CONCURRENT_MESSAGES = "true";
CONCURRENT_MESSAGE_MAX = "2";
LIMIT_MESSAGE_IP = "true";
MESSAGE_IP_MAX = "40";
MESSAGE_IP_WINDOW = "1";
LIMIT_MESSAGE_USER = "false";
MESSAGE_USER_MAX = "40";
MESSAGE_USER_WINDOW = "1";
ILLEGAL_MODEL_REQ_SCORE = "5";
# Registration and login
ALLOW_EMAIL_LOGIN = "true";
ALLOW_REGISTRATION = "false";
ALLOW_SOCIAL_LOGIN = "false";
ALLOW_SOCIAL_REGISTRATION = "false";
ALLOW_PASSWORD_RESET = "false";
ALLOW_UNVERIFIED_EMAIL_LOGIN = "true";
SESSION_EXPIRY = "1000 * 60 * 15";
REFRESH_TOKEN_EXPIRY = "(1000 * 60 * 60 * 24) * 7";
# OpenID
OPENID_SCOPE = "openid profile email";
OPENID_CALLBACK_URL = "/oauth/openid/callback";
OPENID_AUTO_REDIRECT = "false";
OPENID_USE_PKCE = "false";
OPENID_ON_BEHALF_FLOW_USERINFO_SCOPE = "user.read";
# OAuth callback URLs
DISCORD_CALLBACK_URL = "/oauth/discord/callback";
FACEBOOK_CALLBACK_URL = "/oauth/facebook/callback";
GITHUB_CALLBACK_URL = "/oauth/github/callback";
GOOGLE_CALLBACK_URL = "/oauth/google/callback";
APPLE_CALLBACK_URL = "/oauth/apple/callback";
SAML_CALLBACK_URL = "/oauth/saml/callback";
# Entra ID
USE_ENTRA_ID_FOR_PEOPLE_SEARCH = "false";
ENTRA_ID_INCLUDE_OWNERS_AS_MEMBERS = "false";
OPENID_GRAPH_SCOPES = "User.Read,People.Read,GroupMember.Read.All";
# Shared links
ALLOW_SHARED_LINKS = "true";
ALLOW_SHARED_LINKS_PUBLIC = "false";
# UI
APP_TITLE = "LibreChat";
HELP_AND_FAQ_URL = "https://librechat.ai";
# Flux
FLUX_API_BASE_URL = "https://api.us1.bfl.ai";
# Email
EMAIL_PORT = "25";
EMAIL_FROM = "noreply@librechat.ai";
# Azure Blob Storage
AZURE_STORAGE_PUBLIC_ACCESS = "false";
AZURE_CONTAINER_NAME = "files";
};
mkImageOption =
name: description:
mkOption {
type = types.package;
default = defaultImages.${name};
inherit description;
};
inherit (lib)
literalExpression
mkEnableOption
mkIf
mkOption
mkOverride
optional
types
;
in
{
options.services.librechat-oci = {
enable = mkEnableOption "LibreChat container with Podman.";
images = {
librechat = mkImageOption "librechat" "The LibreChat Docker image (`pkgs.dockerTools.pullImage`).";
meilisearch = mkImageOption "meilisearch" "The Meilisearch Docker image (`pkgs.dockerTools.pullImage`).";
mongodb = mkImageOption "mongodb" "The MongoDB Docker image (`pkgs.dockerTools.pullImage`).";
vectordb = mkImageOption "vectordb" "The pgvector Docker image (`pkgs.dockerTools.pullImage`).";
ragApi = mkImageOption "ragApi" "The LibreChat RAG API Docker image (`pkgs.dockerTools.pullImage`).";
};
configFile = mkOption {
type = types.nullOr types.path;
default = null;
example = literalExpression "./librechat.yaml";
description = "Path to the `librechat.yaml` configuration file.";
};
externalUrl = mkOption {
type = types.nullOr types.str;
default = null;
example = literalExpression ''"https://chat.example.com"'';
description = "Public URL to configure for LibreChat (sets DOMAIN_CLIENT and DOMAIN_SERVER).";
};
port = mkOption {
type = types.port;
default = 3080;
description = "Which port the LibreChat server listens on.";
};
meiliPort = mkOption {
type = types.port;
default = 7700;
description = "Which port Meilisearch listens on.";
};
ragPort = mkOption {
type = types.port;
default = 8000;
description = "Which port the RAG API listens on.";
};
environment = mkOption {
default = { };
type = types.attrsOf types.str;
description = ''
Extra environment variables for LibreChat.
These are merged on top of the defaults and can override them.
For secrets use <option>environmentFile</option> instead.
See <https://docs.librechat.ai/docs/configuration/dotenv>.
'';
};
environmentFile = mkOption {
type = types.nullOr types.path;
default = null;
example = literalExpression "config.sops.templates.librechat-env.path";
description = ''
Environment file passed to the LibreChat, Meilisearch, and RAG API
containers. Use this for secrets such as JWT_SECRET, CREDS_KEY,
MEILI_MASTER_KEY, and API keys.
'';
};
};
config = mkIf cfg.enable {
virtualisation = {
podman = {
enable = true;
autoPrune.enable = true;
dockerCompat = true;
};
oci-containers = {
backend = "podman";
containers = {
librechat-mongodb = {
image = with cfg.images.mongodb; "${imageName}:${imageTag}";
imageFile = cfg.images.mongodb;
cmd = [
"mongod"
"--noauth"
];
volumes = [
"librechat_mongodb_data:/data/db:rw"
];
log-driver = "journald";
extraOptions = [
"--network-alias=mongodb"
"--network=librechat_default"
];
};
librechat-meilisearch = {
image = with cfg.images.meilisearch; "${imageName}:${imageTag}";
imageFile = cfg.images.meilisearch;
environment = {
MEILI_NO_ANALYTICS = "true";
MEILI_HOST = "http://meilisearch:${toString cfg.meiliPort}";
};
environmentFiles = optional (cfg.environmentFile != null) cfg.environmentFile;
volumes = [
"librechat_meili_data:/meili_data:rw"
];
log-driver = "journald";
extraOptions = [
"--network-alias=meilisearch"
"--network=librechat_default"
];
};
librechat-vectordb = {
image = with cfg.images.vectordb; "${imageName}:${imageTag}";
imageFile = cfg.images.vectordb;
environment = {
POSTGRES_DB = "mydatabase";
POSTGRES_USER = "myuser";
POSTGRES_PASSWORD = "mypassword";
};
volumes = [
"librechat_pgdata:/var/lib/postgresql/data:rw"
];
log-driver = "journald";
extraOptions = [
"--network-alias=vectordb"
"--network=librechat_default"
];
};
librechat-rag-api = {
image = with cfg.images.ragApi; "${imageName}:${imageTag}";
imageFile = cfg.images.ragApi;
environment = {
DB_HOST = "vectordb";
RAG_PORT = toString cfg.ragPort;
};
environmentFiles = optional (cfg.environmentFile != null) cfg.environmentFile;
dependsOn = [ "librechat-vectordb" ];
log-driver = "journald";
extraOptions = [
"--network-alias=rag_api"
"--network=librechat_default"
];
};
librechat = {
image = with cfg.images.librechat; "${imageName}:${imageTag}";
imageFile = cfg.images.librechat;
environment =
defaultEnv
// {
MONGO_URI = "mongodb://mongodb:27017/LibreChat";
MEILI_HOST = "http://meilisearch:${toString cfg.meiliPort}";
RAG_PORT = toString cfg.ragPort;
RAG_API_URL = "http://rag_api:${toString cfg.ragPort}";
DOMAIN_CLIENT = if cfg.externalUrl != null then cfg.externalUrl else "http://localhost:${toString cfg.port}";
DOMAIN_SERVER = if cfg.externalUrl != null then cfg.externalUrl else "http://localhost:${toString cfg.port}";
}
// cfg.environment;
environmentFiles = optional (cfg.environmentFile != null) cfg.environmentFile;
volumes = [
"librechat_images:/app/client/public/images:rw"
"librechat_uploads:/app/uploads:rw"
"librechat_logs:/app/logs:rw"
]
++ optional (cfg.configFile != null) "${cfg.configFile}:/app/librechat.yaml:ro";
ports = [
"0.0.0.0:${toString cfg.port}:${toString cfg.port}/tcp"
];
dependsOn = [
"librechat-mongodb"
"librechat-rag-api"
];
log-driver = "journald";
extraOptions = [
"--network-alias=api"
"--network=librechat_default"
];
};
};
};
};
networking.firewall.interfaces =
let
matchAll = if !config.networking.nftables.enable then "podman+" else "podman*";
in
{
"${matchAll}".allowedUDPPorts = [ 53 ];
};
systemd = {
services = {
podman-librechat-mongodb = {
serviceConfig.Restart = mkOverride 90 "always";
after = [
"podman-network-librechat_default.service"
"podman-volume-librechat_mongodb_data.service"
];
requires = [
"podman-network-librechat_default.service"
"podman-volume-librechat_mongodb_data.service"
];
partOf = [ "podman-compose-librechat-root.target" ];
wantedBy = [ "podman-compose-librechat-root.target" ];
};
podman-librechat-meilisearch = {
serviceConfig.Restart = mkOverride 90 "always";
after = [
"podman-network-librechat_default.service"
"podman-volume-librechat_meili_data.service"
];
requires = [
"podman-network-librechat_default.service"
"podman-volume-librechat_meili_data.service"
];
partOf = [ "podman-compose-librechat-root.target" ];
wantedBy = [ "podman-compose-librechat-root.target" ];
};
podman-librechat-vectordb = {
serviceConfig.Restart = mkOverride 90 "always";
after = [
"podman-network-librechat_default.service"
"podman-volume-librechat_pgdata.service"
];
requires = [
"podman-network-librechat_default.service"
"podman-volume-librechat_pgdata.service"
];
partOf = [ "podman-compose-librechat-root.target" ];
wantedBy = [ "podman-compose-librechat-root.target" ];
};
podman-librechat-rag-api = {
serviceConfig.Restart = mkOverride 90 "always";
after = [
"podman-network-librechat_default.service"
"podman-librechat-vectordb.service"
];
requires = [
"podman-network-librechat_default.service"
"podman-librechat-vectordb.service"
];
partOf = [ "podman-compose-librechat-root.target" ];
wantedBy = [ "podman-compose-librechat-root.target" ];
};
podman-librechat = {
serviceConfig.Restart = mkOverride 90 "always";
after = [
"podman-network-librechat_default.service"
"podman-volume-librechat_images.service"
"podman-volume-librechat_uploads.service"
"podman-volume-librechat_logs.service"
"podman-librechat-mongodb.service"
"podman-librechat-meilisearch.service"
"podman-librechat-rag-api.service"
];
requires = [
"podman-network-librechat_default.service"
"podman-volume-librechat_images.service"
"podman-volume-librechat_uploads.service"
"podman-volume-librechat_logs.service"
"podman-librechat-mongodb.service"
"podman-librechat-meilisearch.service"
"podman-librechat-rag-api.service"
];
partOf = [ "podman-compose-librechat-root.target" ];
wantedBy = [ "podman-compose-librechat-root.target" ];
};
podman-network-librechat_default = {
path = [ pkgs.podman ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStop = "podman network rm -f librechat_default";
};
script = ''
podman network inspect librechat_default || podman network create librechat_default
'';
partOf = [ "podman-compose-librechat-root.target" ];
wantedBy = [ "podman-compose-librechat-root.target" ];
};
podman-volume-librechat_mongodb_data = {
path = [ pkgs.podman ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = "podman volume inspect librechat_mongodb_data || podman volume create librechat_mongodb_data";
partOf = [ "podman-compose-librechat-root.target" ];
wantedBy = [ "podman-compose-librechat-root.target" ];
};
podman-volume-librechat_meili_data = {
path = [ pkgs.podman ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = "podman volume inspect librechat_meili_data || podman volume create librechat_meili_data";
partOf = [ "podman-compose-librechat-root.target" ];
wantedBy = [ "podman-compose-librechat-root.target" ];
};
podman-volume-librechat_pgdata = {
path = [ pkgs.podman ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = "podman volume inspect librechat_pgdata || podman volume create librechat_pgdata";
partOf = [ "podman-compose-librechat-root.target" ];
wantedBy = [ "podman-compose-librechat-root.target" ];
};
podman-volume-librechat_images = {
path = [ pkgs.podman ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = "podman volume inspect librechat_images || podman volume create librechat_images";
partOf = [ "podman-compose-librechat-root.target" ];
wantedBy = [ "podman-compose-librechat-root.target" ];
};
podman-volume-librechat_uploads = {
path = [ pkgs.podman ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = "podman volume inspect librechat_uploads || podman volume create librechat_uploads";
partOf = [ "podman-compose-librechat-root.target" ];
wantedBy = [ "podman-compose-librechat-root.target" ];
};
podman-volume-librechat_logs = {
path = [ pkgs.podman ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = "podman volume inspect librechat_logs || podman volume create librechat_logs";
partOf = [ "podman-compose-librechat-root.target" ];
wantedBy = [ "podman-compose-librechat-root.target" ];
};
};
targets.podman-compose-librechat-root = {
unitConfig.Description = "Root target generated by compose2nix.";
wantedBy = [ "multi-user.target" ];
};
};
};
}

View file

@ -8,7 +8,7 @@
let
cfg = config.mailserver;
inherit (config.networking) domain;
domain = config.networking.domain;
fqdn = "${cfg.subdomain}.${domain}";
inherit (lib)
@ -74,7 +74,7 @@ in
nameValuePair "${user}@${domain}" {
name = "${user}@${domain}";
aliases = map (alias: "${alias}@${domain}") (accConf.aliases or [ ]);
inherit (accConf) sendOnly;
sendOnly = accConf.sendOnly;
quota = mkDefault "5G";
hashedPasswordFile = config.sops.secrets."mailserver/accounts/${user}".path;
}

View file

@ -23,7 +23,7 @@ let
services."mautrix-${name}" = {
enable = true;
inherit (cfg.bridges.${name}) package;
package = cfg.bridges.${name}.package;
environmentFile = mkIf cfg.sops config.sops.templates."mautrix-${name}/env-file".path;
settings = {
bridge = {
@ -71,29 +71,33 @@ let
mode = "0400";
in
{
secrets = {
"mautrix-${name}/encryption-pickle-key" = {
inherit owner group mode;
};
"mautrix-${name}/provisioning-shared-secret" = {
inherit owner group mode;
};
"mautrix-${name}/public-media-signing-key" = {
inherit owner group mode;
};
"mautrix-${name}/direct-media-server-key" = {
inherit owner group mode;
};
secrets."mautrix-${name}/encryption-pickle-key" = {
inherit owner group mode;
};
secrets."mautrix-${name}/provisioning-shared-secret" = {
inherit owner group mode;
};
secrets."mautrix-${name}/public-media-signing-key" = {
inherit owner group mode;
};
secrets."mautrix-${name}/direct-media-server-key" = {
inherit owner group mode;
};
templates."mautrix-${name}/env-file" = {
inherit owner group mode;
content = ''
MAUTRIX_${toUpper name}_ENCRYPTION_PICKLE_KEY=${config.sops.placeholder."mautrix-${name}/encryption-pickle-key"}
MAUTRIX_${toUpper name}_ENCRYPTION_PICKLE_KEY=${
config.sops.placeholder."mautrix-${name}/encryption-pickle-key"
}
MAUTRIX_${toUpper name}_PROVISIONING_SHARED_SECRET=${
config.sops.placeholder."mautrix-${name}/provisioning-shared-secret"
}
MAUTRIX_${toUpper name}_PUBLIC_MEDIA_SIGNING_KEY=${config.sops.placeholder."mautrix-${name}/public-media-signing-key"}
MAUTRIX_${toUpper name}_DIRECT_MEDIA_SERVER_KEY=${config.sops.placeholder."mautrix-${name}/direct-media-server-key"}
MAUTRIX_${toUpper name}_PUBLIC_MEDIA_SIGNING_KEY=${
config.sops.placeholder."mautrix-${name}/public-media-signing-key"
}
MAUTRIX_${toUpper name}_DIRECT_MEDIA_SERVER_KEY=${
config.sops.placeholder."mautrix-${name}/direct-media-server-key"
}
'';
};
}

View file

@ -84,74 +84,77 @@ in
};
config = mkIf cfg.enable {
services = {
postgresql = {
enable = true;
initialScript = pkgs.writeText "synapse-init.sql" ''
CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD 'synapse';
CREATE DATABASE "matrix-synapse" WITH OWNER "matrix-synapse"
TEMPLATE template0
LC_COLLATE = 'C'
LC_CTYPE = 'C';
'';
};
services.postgresql = {
enable = true;
initialScript = pkgs.writeText "synapse-init.sql" ''
CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD 'synapse';
CREATE DATABASE "matrix-synapse" WITH OWNER "matrix-synapse"
TEMPLATE template0
LC_COLLATE = 'C'
LC_CTYPE = 'C';
'';
};
matrix-synapse = mkMerge [
{
settings = {
registration_shared_secret_path = mkIf cfg.sops config.sops.secrets."matrix/registration-shared-secret".path;
server_name = config.networking.domain;
public_baseurl = baseUrl;
listeners = [
{
inherit (cfg) port;
bind_addresses = [ "127.0.0.1" ];
resources = [
{
compress = true;
names = [ "client" ];
}
{
compress = false;
names = [ "federation" ];
}
];
tls = false;
type = "http";
x_forwarded = true;
}
];
};
}
(mkIf cfg.coturn.enable {
settings = {
turn_uris = with cfg.coturn; [
"turn:${realm}:${toString listening-port}?transport=udp"
"turn:${realm}:${toString listening-port}?transport=tcp"
"turn:${realm}:${toString tls-listening-port}?transport=udp"
"turn:${realm}:${toString tls-listening-port}?transport=tcp"
"turn:${realm}:${toString alt-listening-port}?transport=udp"
"turn:${realm}:${toString alt-listening-port}?transport=tcp"
"turn:${realm}:${toString alt-tls-listening-port}?transport=udp"
"turn:${realm}:${toString alt-tls-listening-port}?transport=tcp"
];
extraConfigFiles = mkIf cfg.sops [ config.sops.templates."coturn/static-auth-secret.env".path ];
turn_user_lifetime = "1h";
};
})
];
nginx.virtualHosts."${cfg.settings.server_name}" = {
enableACME = true;
forceSSL = true;
locations = {
"= /.well-known/matrix/server".extraConfig = mkWellKnown serverConfig;
"= /.well-known/matrix/client".extraConfig = mkWellKnown clientConfig;
"/_matrix".proxyPass = "http://127.0.0.1:${toString cfg.port}";
"/_synapse".proxyPass = "http://127.0.0.1:${toString cfg.port}";
services.matrix-synapse = mkMerge [
{
settings = {
registration_shared_secret_path =
mkIf cfg.sops
config.sops.secrets."matrix/registration-shared-secret".path;
server_name = config.networking.domain;
public_baseurl = baseUrl;
listeners = [
{
inherit (cfg) port;
bind_addresses = [ "127.0.0.1" ];
resources = [
{
compress = true;
names = [ "client" ];
}
{
compress = false;
names = [ "federation" ];
}
];
tls = false;
type = "http";
x_forwarded = true;
}
];
};
};
}
(mkIf cfg.coturn.enable {
settings = {
turn_uris = with cfg.coturn; [
"turn:${realm}:${toString listening-port}?transport=udp"
"turn:${realm}:${toString listening-port}?transport=tcp"
"turn:${realm}:${toString tls-listening-port}?transport=udp"
"turn:${realm}:${toString tls-listening-port}?transport=tcp"
"turn:${realm}:${toString alt-listening-port}?transport=udp"
"turn:${realm}:${toString alt-listening-port}?transport=tcp"
"turn:${realm}:${toString alt-tls-listening-port}?transport=udp"
"turn:${realm}:${toString alt-tls-listening-port}?transport=tcp"
];
extraConfigFiles = mkIf cfg.sops [ config.sops.templates."coturn/static-auth-secret.env".path ];
turn_user_lifetime = "1h";
};
})
];
environment.shellAliases = mkIf cfg.sops {
register_new_matrix_user = "${cfg.package}/bin/register_new_matrix_user -k $(sudo cat ${cfg.settings.registration_shared_secret_path})";
};
services.nginx.virtualHosts."${cfg.settings.server_name}" = {
enableACME = true;
forceSSL = true;
locations."= /.well-known/matrix/server".extraConfig = mkWellKnown serverConfig;
locations."= /.well-known/matrix/client".extraConfig = mkWellKnown clientConfig;
locations."/_matrix".proxyPass = "http://127.0.0.1:${toString cfg.port}";
locations."/_synapse".proxyPass = "http://127.0.0.1:${toString cfg.port}";
};
sops = mkIf cfg.sops {

View file

@ -6,53 +6,51 @@
let
cfg = config.services.matrix-synapse;
inherit (config.networking) domain;
domain = config.networking.domain;
inherit (lib) mkIf mkDefault;
in
{
config = mkIf cfg.enable {
services = {
livekit = {
enable = true;
settings.port = mkDefault 7880;
settings.room.auto_create = mkDefault false;
openFirewall = mkDefault true;
keyFile = mkIf cfg.sops config.sops.templates."livekit/key".path;
};
services.livekit = {
enable = true;
settings.port = mkDefault 7880;
settings.room.auto_create = mkDefault false;
openFirewall = mkDefault true;
keyFile = mkIf cfg.sops config.sops.templates."livekit/key".path;
};
lk-jwt-service = {
enable = true;
port = mkDefault 8080;
livekitUrl = "wss://${domain}/livekit/sfu";
keyFile = mkIf cfg.sops config.sops.templates."livekit/key".path;
};
nginx.virtualHosts = {
"${domain}".locations = {
"^~ /livekit/jwt/" = {
priority = 400;
proxyPass = "http://127.0.0.1:${toString config.services.lk-jwt-service.port}/";
};
"^~ /livekit/sfu/" = {
priority = 400;
proxyPass = "http://127.0.0.1:${toString config.services.livekit.settings.port}/";
proxyWebsockets = true;
extraConfig = ''
proxy_send_timeout 120;
proxy_read_timeout 120;
proxy_buffering off;
proxy_set_header Accept-Encoding gzip;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
'';
};
};
};
services.lk-jwt-service = {
enable = true;
port = mkDefault 8080;
livekitUrl = "wss://${domain}/livekit/sfu";
keyFile = mkIf cfg.sops config.sops.templates."livekit/key".path;
};
systemd.services.lk-jwt-service.environment.LIVEKIT_FULL_ACCESS_HOMESERVERS = domain;
services.nginx.virtualHosts = {
"${domain}".locations = {
"^~ /livekit/jwt/" = {
priority = 400;
proxyPass = "http://127.0.0.1:${toString config.services.lk-jwt-service.port}/";
};
"^~ /livekit/sfu/" = {
priority = 400;
proxyPass = "http://127.0.0.1:${toString config.services.livekit.settings.port}/";
proxyWebsockets = true;
extraConfig = ''
proxy_send_timeout 120;
proxy_read_timeout 120;
proxy_buffering off;
proxy_set_header Accept-Encoding gzip;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
'';
};
};
};
sops = mkIf cfg.sops {
secrets."livekit/key" = { };
templates."livekit/key".content = ''

View file

@ -76,7 +76,7 @@ in
sops = mkIf cfg.sops (
let
owner = user.name;
inherit (user) group;
group = user.group;
mode = "0400";
in
{
@ -98,7 +98,9 @@ in
''
admins:
''
+ concatLines (map (admin: " ${admin}: ${config.sops.placeholder."maubot/admins/${admin}"}") cfg.admins)
+ concatLines (
map (admin: " ${admin}: ${config.sops.placeholder."maubot/admins/${admin}"}") cfg.admins
)
);
};
}

View file

@ -28,12 +28,6 @@ in
default = null;
};
port = mkOption {
type = types.port;
default = 8000;
description = "Port on which the mcpo service should listen.";
};
user = mkOption {
type = types.str;
description = "The user the mcpo service will run as.";
@ -96,7 +90,7 @@ in
users.users."${cfg.user}" = {
isSystemUser = true;
inherit (cfg) group;
group = cfg.group;
};
users.groups."${cfg.group}" = { };
@ -106,7 +100,7 @@ in
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart = "${getExe cfg.package} --port ${toString cfg.port} --config ${configFile}";
ExecStart = "${getExe cfg.package} --config ${configFile}";
Restart = "on-failure";
User = cfg.user;
Group = cfg.group;

View file

@ -6,8 +6,8 @@
let
cfg = config.services.miniflux;
inherit (config.networking) domain;
inherit (cfg.reverseProxy) subdomain;
domain = config.networking.domain;
subdomain = cfg.reverseProxy.subdomain;
fqdn = if (cfg.reverseProxy.enable && subdomain != "") then "${subdomain}.${domain}" else domain;
port = 8085;

View file

@ -50,7 +50,8 @@ in
"1.1.1.1"
"2606:4700:4700::1111"
];
resolvers = if config.networking.nameservers == [ ] then cloudflare else config.networking.nameservers;
resolvers =
if config.networking.nameservers == [ ] then cloudflare else config.networking.nameservers;
in
map escapeIPv6 resolvers;
@ -59,7 +60,7 @@ in
virtualHosts = {
"${config.networking.domain}" = mkDefault {
enableACME = cfg.forceSSL;
inherit (cfg) forceSSL;
forceSSL = cfg.forceSSL;
};
};
};

View file

@ -21,7 +21,7 @@ in
types.submodule {
options = {
extraGroups = mkOption {
type = types.listOf types.str;
type = (types.listOf types.str);
default = [ ];
description = "Extra groups for the user";
example = [ "wheel" ];
@ -37,7 +37,7 @@ in
description = "Initial password for the user";
};
sshKeyFiles = mkOption {
type = types.listOf types.path;
type = (types.listOf types.path);
default = [ ];
description = "SSH key files for the user";
example = [ "/path/to/id_rsa.pub" ];

View file

@ -1,56 +0,0 @@
{ config, lib, ... }:
let
cfg = config.services.nostr-relay;
inherit (config.networking) domain;
inherit (cfg.reverseProxy) subdomain;
fqdn = if (cfg.reverseProxy.enable && subdomain != "") then "${subdomain}.${domain}" else domain;
inherit (lib)
mkDefault
mkIf
;
inherit (lib.utils)
mkReverseProxyOption
;
in
{
options.services.nostr-relay = {
reverseProxy = mkReverseProxyOption "Nostr Relay" "nostr";
};
config = mkIf cfg.enable {
services.nostr-rs-relay = {
settings = {
network = {
port = mkDefault 12849;
host = mkDefault (if cfg.reverseProxy.enable then "127.0.0.1" else "0.0.0.0");
};
limits = {
max_event_size = mkDefault 65536;
max_subscriptions = mkDefault 20;
max_filters = mkDefault 10;
max_subid_length = mkDefault 128;
max_event_tags = mkDefault 2000;
max_content_length = mkDefault 32768;
};
federation = {
enabled = mkDefault true;
max_message_size = mkDefault 1048576;
};
database = {
max_query_time_ms = mkDefault 5000;
};
};
};
services.nginx.virtualHosts = mkIf cfg.reverseProxy.enable {
"${fqdn}" = {
enableACME = cfg.reverseProxy.forceSSL;
inherit (cfg.reverseProxy) forceSSL;
locations."/".proxyPass = "http://127.0.0.1:${toString config.services.nostr-rs-relay.settings.network.port}";
};
};
};
}

View file

@ -9,26 +9,20 @@ let
inherit (lib) mkDefault;
in
{
boot = {
blacklistedKernelModules = [ "nouveau" ];
extraModulePackages = [ config.hardware.nvidia.package ];
initrd.kernelModules = [ "nvidia" ];
};
boot.blacklistedKernelModules = [ "nouveau" ];
boot.extraModulePackages = [ config.hardware.nvidia.package ];
boot.initrd.kernelModules = [ "nvidia" ];
environment.systemPackages = with pkgs; [
nvtopPackages.nvidia
];
hardware = {
enableRedistributableFirmware = true;
graphics.enable = true;
nvidia = {
modesetting.enable = true;
nvidiaSettings = true;
open = false;
package = mkDefault config.boot.kernelPackages.nvidiaPackages.latest;
};
};
hardware.enableRedistributableFirmware = true;
hardware.graphics.enable = true;
hardware.nvidia.modesetting.enable = true;
hardware.nvidia.nvidiaSettings = true;
hardware.nvidia.open = false;
hardware.nvidia.package = mkDefault config.boot.kernelPackages.nvidiaPackages.latest;
nixpkgs.config.cudaSupport = true;

View file

@ -2,8 +2,8 @@
let
cfg = config.services.ollama;
inherit (config.networking) domain;
inherit (cfg.reverseProxy) subdomain;
domain = config.networking.domain;
subdomain = cfg.reverseProxy.subdomain;
fqdn = if (cfg.reverseProxy.enable && subdomain != "") then "${subdomain}.${domain}" else domain;
inherit (lib)
@ -31,7 +31,7 @@ in
services.nginx.virtualHosts = mkIf cfg.reverseProxy.enable {
"${fqdn}" = mkVirtualHost {
inherit (cfg) port;
port = cfg.port;
ssl = cfg.reverseProxy.forceSSL;
recommendedProxySettings = mkForce false;
extraConfig = ''

View file

@ -10,10 +10,10 @@ let
image = pkgs.dockerTools.pullImage {
imageName = "ghcr.io/open-webui/open-webui";
imageDigest = "sha256:a7e4796ae894d1e2a0c1824860ade472f35c507608a01c3581377b5c19b0ed49";
hash = "sha256-uhPlVXSxY6rGbYGvlPVV3zurmbI96mAHEuKKy9FFaD4=";
imageDigest = "sha256:2deb90b0423473d8f97febced2e62b8fd898aa3eb61877bb3aa336370214c258";
hash = "sha256-2cdKfvZGUyPUm7TFXcf5OcG9ey4BvOZPUOVim1S2C+s=";
finalImageName = "ghcr.io/open-webui/open-webui";
finalImageTag = "0.9.2";
finalImageTag = "0.8.5";
};
defaultEnv = {
@ -83,43 +83,10 @@ in
};
config = mkIf cfg.enable {
virtualisation = {
podman = {
enable = true;
autoPrune.enable = true;
dockerCompat = true;
};
oci-containers = {
backend = "podman";
containers."open-webui" = {
image = with cfg.image; imageName + ":" + imageTag;
imageFile = cfg.image;
environment =
defaultEnv
// cfg.environment
// {
PORT = "${toString cfg.port}";
CORS_ALLOW_ORIGIN = concatStringsSep ";" (
[
"http://localhost:${toString cfg.port}"
"http://127.0.0.1:${toString cfg.port}"
"http://0.0.0.0:${toString cfg.port}"
]
++ optional (cfg.externalUrl != null) cfg.externalUrl
);
};
environmentFiles = optional (cfg.environmentFile != null) cfg.environmentFile;
volumes = [
"open-webui_open-webui:/app/backend/data:rw"
];
log-driver = "journald";
extraOptions = [
"--network=host"
];
};
};
virtualisation.podman = {
enable = true;
autoPrune.enable = true;
dockerCompat = true;
};
networking.firewall.interfaces =
@ -130,44 +97,70 @@ in
"${matchAll}".allowedUDPPorts = [ 53 ];
};
systemd = {
services."podman-open-webui" = {
serviceConfig = {
Restart = mkOverride 90 "always";
};
after = [
"podman-volume-open-webui_open-webui.service"
];
requires = [
"podman-volume-open-webui_open-webui.service"
];
partOf = [
"podman-compose-open-webui-root.target"
];
wantedBy = [
"podman-compose-open-webui-root.target"
];
};
virtualisation.oci-containers.backend = "podman";
services."podman-volume-open-webui_open-webui" = {
path = [ pkgs.podman ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
virtualisation.oci-containers.containers."open-webui" = {
image = with cfg.image; imageName + ":" + imageTag;
imageFile = cfg.image;
environment =
defaultEnv
// cfg.environment
// {
PORT = "${toString cfg.port}";
CORS_ALLOW_ORIGIN = concatStringsSep ";" (
[
"http://localhost:${toString cfg.port}"
"http://127.0.0.1:${toString cfg.port}"
"http://0.0.0.0:${toString cfg.port}"
]
++ optional (cfg.externalUrl != null) cfg.externalUrl
);
};
script = ''
podman volume inspect open-webui_open-webui || podman volume create open-webui_open-webui
'';
partOf = [ "podman-compose-open-webui-root.target" ];
wantedBy = [ "podman-compose-open-webui-root.target" ];
environmentFiles = optional (cfg.environmentFile != null) cfg.environmentFile;
volumes = [
"open-webui_open-webui:/app/backend/data:rw"
];
log-driver = "journald";
extraOptions = [
"--network=host"
];
};
systemd.services."podman-open-webui" = {
serviceConfig = {
Restart = mkOverride 90 "always";
};
after = [
"podman-volume-open-webui_open-webui.service"
];
requires = [
"podman-volume-open-webui_open-webui.service"
];
partOf = [
"podman-compose-open-webui-root.target"
];
wantedBy = [
"podman-compose-open-webui-root.target"
];
};
targets."podman-compose-open-webui-root" = {
unitConfig = {
Description = "Root target generated by compose2nix.";
};
wantedBy = [ "multi-user.target" ];
systemd.services."podman-volume-open-webui_open-webui" = {
path = [ pkgs.podman ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
podman volume inspect open-webui_open-webui || podman volume create open-webui_open-webui
'';
partOf = [ "podman-compose-open-webui-root.target" ];
wantedBy = [ "podman-compose-open-webui-root.target" ];
};
systemd.targets."podman-compose-open-webui-root" = {
unitConfig = {
Description = "Root target generated by compose2nix.";
};
wantedBy = [ "multi-user.target" ];
};
};
}

View file

@ -7,8 +7,8 @@
let
cfg = config.services.print-server;
inherit (config.networking) domain;
inherit (cfg.reverseProxy) subdomain;
domain = config.networking.domain;
subdomain = cfg.reverseProxy.subdomain;
fqdn = if (cfg.reverseProxy.enable && subdomain != "") then "${subdomain}.${domain}" else domain;
port = 631;
@ -36,49 +36,47 @@ in
};
config = mkIf cfg.enable {
services = {
printing = {
enable = true;
listenAddresses = [ "*:${builtins.toString port}" ];
webInterface = true;
tempDir = "/tmp/cups";
allowFrom = [ "all" ];
snmpConf = ''
Address @LOCAL
'';
clientConf = "";
inherit (cfg) openFirewall;
drivers = with pkgs; [
brlaser
brgenml1lpr
brgenml1cupswrapper # Brother
postscript-lexmark # Lexmark
hplip
hplipWithPlugin # HP
splix
samsung-unified-linux-driver # Samsung
gutenprint
gutenprintBin # different vendors
];
defaultShared = true;
browsing = true;
browsedConf = ''
BrowsePoll ${fqdn}
'';
};
services.printing = {
enable = true;
listenAddresses = [ "*:${builtins.toString port}" ];
webInterface = true;
tempDir = "/tmp/cups";
allowFrom = [ "all" ];
snmpConf = ''
Address @LOCAL
'';
clientConf = "";
openFirewall = cfg.openFirewall;
drivers = with pkgs; [
brlaser
brgenml1lpr
brgenml1cupswrapper # Brother
postscript-lexmark # Lexmark
hplip
hplipWithPlugin # HP
splix
samsung-unified-linux-driver # Samsung
gutenprint
gutenprintBin # different vendors
];
defaultShared = true;
browsing = true;
browsedConf = ''
BrowsePoll ${fqdn}
'';
};
# autodiscovery of network printers
avahi = {
enable = true;
nssmdns4 = true;
inherit (cfg) openFirewall;
};
# autodiscovery of network printers
services.avahi = {
enable = true;
nssmdns4 = true;
openFirewall = cfg.openFirewall;
};
nginx.virtualHosts = mkIf cfg.reverseProxy.enable {
${fqdn} = mkVirtualHost {
inherit port;
ssl = cfg.reverseProxy.forceSSL;
};
services.nginx.virtualHosts = mkIf cfg.reverseProxy.enable {
${fqdn} = mkVirtualHost {
inherit port;
ssl = cfg.reverseProxy.forceSSL;
};
};
};

View file

@ -7,8 +7,8 @@
let
cfg = config.services.radicale;
inherit (config.networking) domain;
inherit (cfg.reverseProxy) subdomain;
domain = config.networking.domain;
subdomain = cfg.reverseProxy.subdomain;
fqdn = if (cfg.reverseProxy.enable && subdomain != "") then "${subdomain}.${domain}" else domain;
port = 5232;
@ -63,7 +63,7 @@ in
services.nginx.virtualHosts = mkIf cfg.reverseProxy.enable {
"${fqdn}" = {
inherit (cfg.reverseProxy) forceSSL;
forceSSL = cfg.reverseProxy.forceSSL;
enableACME = cfg.reverseProxy.forceSSL;
locations = {
"/" = {

View file

@ -2,8 +2,8 @@
let
cfg = config.services.rss-bridge;
inherit (config.networking) domain;
inherit (cfg.reverseProxy) subdomain;
domain = config.networking.domain;
subdomain = cfg.reverseProxy.subdomain;
fqdn = if (cfg.reverseProxy.enable && subdomain != "") then "${subdomain}.${domain}" else domain;
inherit (lib)
@ -12,6 +12,7 @@ let
inherit (lib.utils)
mkReverseProxyOption
mkVirtualHost
;
in
{
@ -31,9 +32,8 @@ in
systemd.tmpfiles.rules = [ "d ${cfg.dataDir} 0755 ${cfg.user} ${cfg.group} -" ];
services.nginx.virtualHosts = mkIf cfg.reverseProxy.enable {
"${fqdn}" = {
enableACME = cfg.reverseProxy.forceSSL;
inherit (cfg.reverseProxy) forceSSL;
"${cfg.virtualHost}" = mkVirtualHost {
ssl = cfg.reverseProxy.forceSSL;
};
};
};

View file

@ -1,17 +1,9 @@
{
config,
lib,
pkgs,
...
}:
{ config, lib, ... }:
let
cfg = config.services.tailscale;
inherit (lib)
concatStrings
filterAttrs
mapAttrsToList
mkIf
mkOption
optional
@ -20,122 +12,39 @@ let
in
{
options.services.tailscale = {
tailnets = mkOption {
default = { };
type = types.attrsOf (
types.submodule {
options = {
loginServer = mkOption {
type = types.str;
description = "Login server for this tailnet.";
};
authKeyFile = mkOption {
type = types.nullOr types.str;
default = null;
description = "Path to auth key secret.";
};
enableSSH = mkOption {
type = types.bool;
default = false;
};
acceptDNS = mkOption {
type = types.bool;
default = true;
};
default = mkOption {
type = types.bool;
default = false;
description = "Connect to this tailnet on boot.";
};
};
}
);
loginServer = mkOption {
type = types.str;
description = "The Tailscale login server to use.";
};
enableSSH = mkOption {
type = types.bool;
default = false;
description = "Enable Tailscale SSH functionality.";
};
acceptDNS = mkOption {
type = types.bool;
default = true;
description = "Enable Tailscale's MagicDNS and custom DNS configuration.";
};
};
config =
let
defaultTailnets = filterAttrs (_: t: t.default) cfg.tailnets;
defaultTailnet = if defaultTailnets == { } then null else builtins.head (builtins.attrValues defaultTailnets);
entries = mapAttrsToList (name: tcfg: ''
TAILNETS["${name}"]="${tcfg.loginServer}|${if tcfg.enableSSH then "true" else "false"}|${
if tcfg.acceptDNS then "true" else "false"
}|${if tcfg.authKeyFile != null then tcfg.authKeyFile else ""}"
'') cfg.tailnets;
tailnetSwitchCli = pkgs.writeShellScriptBin "tailnet-switch" ''
set -euo pipefail
TAILSCALE="${cfg.package}/bin/tailscale"
declare -A TAILNETS
${concatStrings entries}
CHOICE="''${1:-}"
if [[ -z "$CHOICE" ]]; then
if [[ -t 0 ]]; then
CHOICE=$(printf '%s\n' "''${!TAILNETS[@]}" | sort | ${pkgs.fzf}/bin/fzf --prompt="Switch tailnet: ")
else
echo "Usage: tailnet-switch <tailnet-name>" >&2
printf 'Available tailnets: %s\n' "''${!TAILNETS[@]}" >&2
exit 1
fi
fi
if [[ -z "''${TAILNETS[$CHOICE]+x}" ]]; then
echo "Unknown tailnet: $CHOICE" >&2
printf 'Available tailnets: %s\n' "''${!TAILNETS[@]}" >&2
exit 1
fi
IFS='|' read -r LOGIN_SERVER ENABLE_SSH ACCEPT_DNS AUTH_KEY_FILE <<< "''${TAILNETS[$CHOICE]}"
echo "Switching to: $CHOICE ($LOGIN_SERVER)"
"$TAILSCALE" down --accept-risk=lose-ssh 2>/dev/null || true
"$TAILSCALE" logout 2>/dev/null || true
UP_FLAGS=("--login-server=$LOGIN_SERVER")
[[ "$ENABLE_SSH" == "true" ]] && UP_FLAGS+=("--ssh")
[[ "$ACCEPT_DNS" == "true" ]] && UP_FLAGS+=("--accept-dns")
[[ -n "$AUTH_KEY_FILE" ]] && UP_FLAGS+=("--auth-key=$(cat "$AUTH_KEY_FILE")")
"$TAILSCALE" up "''${UP_FLAGS[@]}"
echo "Switched to tailnet: $CHOICE"
'';
in
mkIf cfg.enable {
assertions = [
{
assertion = (builtins.length (builtins.attrValues (filterAttrs (_: t: t.default) cfg.tailnets))) <= 1;
message = "services.tailscale.tailnets: Only one tailnet can be set as default.";
}
{
assertion = cfg.tailnets != { };
message = "services.tailscale.tailnets: At least one tailnet must be defined.";
}
];
services.tailscale = mkIf (defaultTailnet != null) (
with defaultTailnet;
{
inherit authKeyFile;
extraSetFlags = optional enableSSH "--ssh" ++ optional acceptDNS "--accept-dns";
extraUpFlags = [
"--login-server=${loginServer}"
]
++ optional enableSSH "--ssh"
++ optional acceptDNS "--accept-dns";
}
);
environment.systemPackages = [ tailnetSwitchCli ];
environment.shellAliases.ts = "${cfg.package}/bin/tailscale";
networking.firewall.trustedInterfaces = [ cfg.interfaceName ];
config = mkIf cfg.enable {
services.tailscale = {
authKeyFile = config.sops.secrets."tailscale/auth-key".path;
extraSetFlags = optional cfg.enableSSH "--ssh" ++ optional cfg.acceptDNS "--accept-dns";
extraUpFlags = [
"--login-server=${cfg.loginServer}"
]
++ optional cfg.enableSSH "--ssh"
++ optional cfg.acceptDNS "--accept-dns";
};
environment.shellAliases = {
ts = "${cfg.package}/bin/tailscale";
};
networking.firewall.trustedInterfaces = [ cfg.interfaceName ];
sops.secrets."tailscale/auth-key" = { };
};
}

View file

@ -58,18 +58,16 @@ in
enable = mkDefault true;
onBoot = mkDefault "ignore";
onShutdown = mkDefault "shutdown";
qemu = {
runAsRoot = mkDefault false;
verbatimConfig = ''
clear_emulation_capabilities = ${boolToZeroOne cfg.libvirtd.clearEmulationCapabilities}
''
+ optionalString (cfg.libvirtd.deviceACL != [ ]) ''
cgroup_device_acl = [
${aclString}
]
'';
swtpm.enable = mkDefault true; # TPM 2.0
};
qemu.runAsRoot = mkDefault false;
qemu.verbatimConfig = ''
clear_emulation_capabilities = ${boolToZeroOne cfg.libvirtd.clearEmulationCapabilities}
''
+ optionalString (cfg.libvirtd.deviceACL != [ ]) ''
cgroup_device_acl = [
${aclString}
]
'';
qemu.swtpm.enable = mkDefault true; # TPM 2.0
};
spiceUSBRedirection.enable = mkDefault true;
};

View file

@ -1,4 +1,3 @@
#!/usr/bin/env bash
shopt -s nullglob
for d in /sys/kernel/iommu_groups/*/devices/*; do
n=${d#*/iommu_groups/*}; n=${n%%/*}

View file

@ -78,32 +78,34 @@ let
};
};
deviceType = types.submodule (
{ config, options, ... }:
{
options = {
resolution = mkOption {
type = types.nullOr resolutionType;
default = null;
description = "Automatically calculate the minimum device size for a specific resolution. Overrides `size` if set.";
deviceType = (
types.submodule (
{ config, options, ... }:
{
options = {
resolution = mkOption {
type = types.nullOr resolutionType;
default = null;
description = "Automatically calculate the minimum device size for a specific resolution. Overrides `size` if set.";
};
size = mkOption {
type = types.number;
description = "Size for the kvmfr device in megabytes.";
};
permissions = mkOption {
type = permissionsType;
default = { };
description = "Permissions of the kvmfr device.";
};
};
size = mkOption {
type = types.number;
description = "Size for the kvmfr device in megabytes.";
config = {
size = mkIf (config.resolution != null) (sizeFromResolution config.resolution);
};
permissions = mkOption {
type = permissionsType;
default = { };
description = "Permissions of the kvmfr device.";
};
};
config = {
size = mkIf (config.resolution != null) (sizeFromResolution config.resolution);
};
}
}
)
);
inherit (lib)
@ -142,7 +144,7 @@ in
'';
"modprobe.d/kvmfr.conf".text = ''
options kvmfr static_size_mb=${concatStringsSep "," (map toString deviceSizes)}
options kvmfr static_size_mb=${concatStringsSep "," (map (size: toString size) deviceSizes)}
'';
"apparmor.d/local/abstractions/libvirt-qemu" = mkIf config.security.apparmor.enable {

View file

@ -2,7 +2,7 @@
let
cfg = config.services.webPage;
inherit (config.networking) domain;
domain = config.networking.domain;
fqdn = if (cfg.subdomain != "") then "${cfg.subdomain}.${domain}" else domain;
nginxUser = config.services.nginx.user;
@ -41,7 +41,7 @@ in
config = mkIf cfg.enable {
services.nginx.virtualHosts."${fqdn}" = {
enableACME = cfg.forceSSL;
inherit (cfg) forceSSL;
forceSSL = cfg.forceSSL;
root = cfg.webRoot;
locations."/".index = "index.html";
sslCertificate = mkIf cfg.forceSSL "${config.security.acme.certs."${fqdn}".directory}/cert.pem";

View file

@ -81,97 +81,90 @@ in
};
config = mkIf cfg.enable {
systemd = {
tmpfiles.rules = [ "d ${cfg.volume} 0755 root podman -" ];
systemd.tmpfiles.rules = [ "d ${cfg.volume} 0755 root podman -" ];
services = {
"podman-windows" = {
serviceConfig = {
Restart = mkOverride 90 "always";
};
after = [
"podman-network-windows_default.service"
];
requires = [
"podman-network-windows_default.service"
];
partOf = [
"podman-compose-windows-root.target"
];
wantedBy = [
"podman-compose-windows-root.target"
];
};
"podman-network-windows_default" = {
path = [ pkgs.podman ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStop = "podman network rm -f windows_default";
};
script = ''
podman network inspect windows_default || podman network create windows_default
'';
partOf = [ "podman-compose-windows-root.target" ];
wantedBy = [ "podman-compose-windows-root.target" ];
};
};
targets."podman-compose-windows-root" = {
unitConfig = {
Description = "Root target generated by compose2nix.";
};
wantedBy = [ "multi-user.target" ];
};
};
virtualisation = {
podman = {
enable = true;
autoPrune.enable = true;
dockerCompat = true;
defaultNetwork.settings = {
dns_enabled = true;
};
};
oci-containers = {
backend = "podman";
containers."windows" = {
image = "dockurr/windows";
environment = with cfg.settings; {
"VERSION" = version;
"RAM_SIZE" = ramSize;
"CPU_CORES" = cpuCores;
"DISK_SIZE" = diskSize;
"USERNAME" = username;
"PASSWORD" = password;
"REGION" = region;
"KEYBOARD" = keyboard;
};
volumes = [
"${cfg.volume}:/storage:rw"
]
++ optional (cfg.sharedVolume != null) "${cfg.sharedVolume}:/shared:rw";
ports = [
"8006:8006/tcp"
"3389:3389/tcp"
"3389:3389/udp"
];
log-driver = "journald";
extraOptions = [
"--cap-add=NET_ADMIN"
"--device=/dev/kvm:/dev/kvm:rwm"
"--device=/dev/net/tun:/dev/net/tun:rwm"
"--network-alias=windows"
"--network=windows_default"
];
};
virtualisation.podman = {
enable = true;
autoPrune.enable = true;
dockerCompat = true;
defaultNetwork.settings = {
dns_enabled = true;
};
};
# https://github.com/NixOS/nixpkgs/issues/226365
networking.firewall.interfaces."podman+".allowedUDPPorts = [ 53 ];
virtualisation.oci-containers.backend = "podman";
virtualisation.oci-containers.containers."windows" = {
image = "dockurr/windows";
environment = with cfg.settings; {
"VERSION" = version;
"RAM_SIZE" = ramSize;
"CPU_CORES" = cpuCores;
"DISK_SIZE" = diskSize;
"USERNAME" = username;
"PASSWORD" = password;
"LANGUAGE" = language;
"REGION" = region;
"KEYBOARD" = keyboard;
};
volumes = [
"${cfg.volume}:/storage:rw"
]
++ optional (cfg.sharedVolume != null) "${cfg.sharedVolume}:/shared:rw";
ports = [
"8006:8006/tcp"
"3389:3389/tcp"
"3389:3389/udp"
];
log-driver = "journald";
extraOptions = [
"--cap-add=NET_ADMIN"
"--device=/dev/kvm:/dev/kvm:rwm"
"--device=/dev/net/tun:/dev/net/tun:rwm"
"--network-alias=windows"
"--network=windows_default"
];
};
systemd.services."podman-windows" = {
serviceConfig = {
Restart = mkOverride 90 "always";
};
after = [
"podman-network-windows_default.service"
];
requires = [
"podman-network-windows_default.service"
];
partOf = [
"podman-compose-windows-root.target"
];
wantedBy = [
"podman-compose-windows-root.target"
];
};
systemd.services."podman-network-windows_default" = {
path = [ pkgs.podman ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStop = "podman network rm -f windows_default";
};
script = ''
podman network inspect windows_default || podman network create windows_default
'';
partOf = [ "podman-compose-windows-root.target" ];
wantedBy = [ "podman-compose-windows-root.target" ];
};
systemd.targets."podman-compose-windows-root" = {
unitConfig = {
Description = "Root target generated by compose2nix.";
};
wantedBy = [ "multi-user.target" ];
};
};
}

View file

@ -0,0 +1,55 @@
{
config,
pkgs,
lib,
...
}:
# Set `networking.hostId` to:
# $ head -c 8 /etc/machine-id
# Mark datasets to snapshot:
# $ sudo zfs set com.sun:auto-snapshot:daily=true dpool/data/backup
# Generate SSH key for replication (empty passphrase):
# $ sudo -i ssh-keygen -t rsa -b 4096 -f /root/.ssh/zfs-replication
let
cfg = config.services.zfs;
inherit (lib) mkDefault mkForce;
in
{
boot.supportedFilesystems = [ "zfs" ];
boot.loader.systemd-boot.enable = mkForce false;
boot.loader.grub.enable = mkForce true;
boot.loader.grub.zfsSupport = mkForce true;
services.zfs.trim = {
enable = mkDefault true;
interval = mkDefault "weekly";
};
services.zfs.scrub = {
enable = mkDefault true;
interval = mkDefault "monthly";
};
services.zfs.autoSnapshot = {
enable = mkDefault true;
flags = mkDefault "-k -p --utc";
frequent = mkDefault 0;
hourly = mkDefault 24;
daily = mkDefault 7;
weekly = mkDefault 4;
monthly = mkDefault 0;
};
services.zfs.autoReplication = {
username = mkDefault "root";
identityFilePath = mkDefault "/root/.ssh/zfs-replication";
followDelete = mkDefault true;
};
environment.systemPackages = with pkgs; [ lz4 ];
}

151
modules/nixos/zfs/disks.sh Normal file
View file

@ -0,0 +1,151 @@
#!/usr/bin/env bash
declare -a SSDs=(
# '/dev/disk/by-id/abc123'
)
declare -a HDDs=(
# '/dev/disk/by-id/def456'
)
declare -a DATA_DATASETS=(
# 'dataset'
)
MNT='/mnt'
SWAP_GB=32
# Helper function to wait for devices
wait_for_device() {
local device=$1
echo "Waiting for device: $device ..."
while [[ ! -e $device ]]; do
sleep 1
done
echo "Device $device is ready."
}
# Function to install a package if it's not already installed
install_if_missing() {
local cmd="$1"
local package="$2"
if ! command -v "$cmd" &> /dev/null; then
echo "$cmd not found, installing $package..."
nix-env -iA "nixos.$package"
fi
}
install_if_missing "sgdisk" "gptfdisk"
install_if_missing "partprobe" "parted"
# Ensure swap parts are off
swapoff --all
udevadm settle
### SSDs ###
echo "Setting up SSDs..."
# Wait for SSD devices to be ready
for ssd in "${SSDs[@]}"; do
wait_for_device "$ssd"
done
# Wipe and partition SSDs
for i in "${!SSDs[@]}"; do
ssd="${SSDs[$i]}"
ssd_num=$((i + 1))
echo "Processing SSD $ssd_num: $ssd"
# Wipe filesystems
wipefs -a "$ssd"
# Clear part tables
sgdisk --zap-all "$ssd"
# Partition disk
sgdisk -n1:1M:+1G -t1:EF00 -c1:BOOT$ssd_num "$ssd"
sgdisk -n2:0:+"$SWAP_GB"G -t2:8200 -c2:SWAP$ssd_num "$ssd"
sgdisk -n3:0:0 -t3:BF00 -c3:ROOT$ssd_num "$ssd"
partprobe -s "$ssd"
udevadm settle
wait_for_device "${ssd}-part3"
done
# Create root pool
echo "Creating root pool..."
zpool create -f -o ashift=12 -o autotrim=on -R "$MNT" -O acltype=posixacl -O canmount=off -O dnodesize=auto -O normalization=formD -O relatime=on -O xattr=sa -O mountpoint=none rpool mirror "${SSDs[0]}"-part3 "${SSDs[1]}"-part3
# Create and mount root system container
zfs create -o canmount=noauto -o mountpoint=legacy rpool/root
mount -o X-mount.mkdir -t zfs rpool/root "$MNT"
# Create root datasets
declare -a ROOT_DATASETS=('home' 'nix' 'tmp' 'var')
for dataset in "${ROOT_DATASETS[@]}"; do
zfs create -o mountpoint=legacy "rpool/$dataset"
mount -o X-mount.mkdir -t zfs "rpool/$dataset" "$MNT/$dataset"
done
# Format boot and swap partitions
for i in "${!SSDs[@]}"; do
ssd="${SSDs[$i]}"
ssd_num=$((i + 1))
mkfs.vfat -F 32 -n BOOT$ssd_num "${ssd}"-part1
mkswap -L SWAP$ssd_num "${ssd}"-part2
swapon -L SWAP$ssd_num
done
# Mount first boot partition
mount -t vfat -o fmask=0077,dmask=0077,iocharset=iso8859-1,X-mount.mkdir -L BOOT1 "$MNT"/boot
### HDDs ###
echo "Setting up HDDs..."
# Wait for HDD devices to be ready
for hdd in "${HDDs[@]}"; do
wait_for_device "$hdd"
done
# Wipe and partition HDDs
for i in "${!HDDs[@]}"; do
hdd="${HDDs[$i]}"
hdd_num=$((i + 1))
echo "Processing HDD $hdd_num: $hdd"
wipefs -a "$hdd"
sgdisk --zap-all "$hdd"
sgdisk -n1:0:0 -t1:BF00 -c1:DATA$hdd_num "$hdd"
done
udevadm settle
# Wait for all HDD partitions to appear
for hdd in "${HDDs[@]}"; do
wait_for_device "${hdd}-part1"
done
# Create data pool
echo "Creating data pool..."
mkdir -p "$MNT"/data
hdd_partitions=()
for hdd in "${HDDs[@]}"; do
hdd_partitions+=("${hdd}-part1")
done
zpool create -f -o ashift=12 -o autotrim=on -R "$MNT" -O acltype=posixacl -O xattr=sa -O dnodesize=auto -O compression=lz4 -O normalization=formD -O relatime=on -O mountpoint=none dpool raidz "${hdd_partitions[@]}"
# Create and mount data root container
zfs create -o canmount=noauto -o mountpoint=legacy dpool/data
mount -o X-mount.mkdir -t zfs dpool/data "$MNT"/data
# Create and mount data datasets
for dataset in "${DATA_DATASETS[@]}"; do
zfs create -o mountpoint=legacy "dpool/data/$dataset"
mount -o X-mount.mkdir -t zfs "dpool/data/$dataset" "$MNT/data/$dataset"
done
echo "Setup complete."

View file

@ -14,78 +14,72 @@ let
;
in
{
nix = {
package = mkDefault pkgs.nix;
nix.package = mkDefault pkgs.nix;
# for `nix run synix#foo`, `nix build synix#bar`, etc
registry = {
synix = {
from = {
id = "synix";
type = "indirect";
};
to = {
owner = "sid";
repo = "synix";
host = "git.sid.ovh";
type = "gitea";
};
# for `nix run synix#foo`, `nix build synix#bar`, etc
nix.registry = {
synix = {
from = {
id = "synix";
type = "indirect";
};
to = {
owner = "sid";
repo = "synix";
host = "git.sid.ovh";
type = "gitea";
};
};
};
settings = {
warn-dirty = mkDefault false;
# fallback quickly if substituters are not available.
nix.settings.connect-timeout = mkDefault 5;
nix.settings.fallback = true;
# fallback quickly if substituters are not available.
connect-timeout = mkDefault 5;
fallback = true;
nix.settings.experimental-features = [
"nix-command"
"flakes"
]
++ optional (
config.nix.package != null && versionOlder (versions.majorMinor config.nix.package.version) "2.22"
) "repl-flake";
experimental-features = [
"nix-command"
"flakes"
]
++ optional (
config.nix.package != null && versionOlder (versions.majorMinor config.nix.package.version) "2.22"
) "repl-flake";
nix.settings.log-lines = mkDefault 25;
log-lines = mkDefault 25;
# avoid disk full issues
nix.settings.max-free = mkDefault (3000 * 1024 * 1024);
nix.settings.min-free = mkDefault (512 * 1024 * 1024);
# avoid disk full issues
max-free = mkDefault (3000 * 1024 * 1024);
min-free = mkDefault (512 * 1024 * 1024);
# avoid copying unnecessary stuff over SSH
nix.settings.builders-use-substitutes = true;
# avoid copying unnecessary stuff over SSH
builders-use-substitutes = true;
# workaround for https://github.com/NixOS/nix/issues/9574
nix.settings.nix-path = config.nix.nixPath;
# workaround for https://github.com/NixOS/nix/issues/9574
nix-path = config.nix.nixPath;
nix.settings.download-buffer-size = 524288000; # 500 MiB
download-buffer-size = 524288000; # 500 MiB
# add all wheel users to the trusted-users group
nix.settings.trusted-users = [
"@wheel"
];
# add all wheel users to the trusted-users group
trusted-users = [
"@wheel"
];
# binary caches
nix.settings.substituters = [
"https://cache.nixos.org"
"https://nix-community.cachix.org"
"https://cache.garnix.io"
"https://numtide.cachix.org"
];
nix.settings.trusted-public-keys = [
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
"cache.garnix.io:CTFPyKSLcx5RMJKfLo5EEPUObbA78b0YQ2DTCJXqr9g="
"numtide.cachix.org-1:2ps1kLBUWjxIneOy1Ik6cQjb41X0iXVXeHigGmycPPE="
];
# binary caches
substituters = [
"https://cache.nixos.org"
"https://nix-community.cachix.org"
"https://cache.garnix.io"
"https://numtide.cachix.org"
];
trusted-public-keys = [
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
"cache.garnix.io:CTFPyKSLcx5RMJKfLo5EEPUObbA78b0YQ2DTCJXqr9g="
"numtide.cachix.org-1:2ps1kLBUWjxIneOy1Ik6cQjb41X0iXVXeHigGmycPPE="
];
};
gc = {
automatic = true;
dates = "weekly";
options = "--delete-older-than 30d";
};
nix.gc = {
automatic = true;
dates = "weekly";
options = "--delete-older-than 30d";
};
}

View file

@ -9,17 +9,11 @@
modifications = final: prev: {
# https://github.com/NixOS/nixpkgs/issues/335003#issuecomment-2755803376
kicad = prev.kicad.override {
stable = true;
};
python312 = prev.python312.override {
packageOverrides = pyFinal: pyPrev: {
arxiv = pyPrev.arxiv.overridePythonAttrs (old: {
pythonRemoveDeps = (old.pythonRemoveDeps or [ ]) ++ [ "requests" ];
});
};
};
kicad = (
prev.kicad.override {
stable = true;
}
);
# bemenu is not a valid selector in v1.5.1
rofi-rbw-wayland = prev.rofi-rbw-wayland.overrideAttrs (oldAttrs: {

View file

@ -1,28 +1,25 @@
{
lib,
python312,
python3,
fetchPypi,
}:
let
python = python312;
in
python.pkgs.buildPythonApplication rec {
python3.pkgs.buildPythonApplication rec {
pname = "arxiv-mcp-server";
version = "0.5.0";
version = "0.3.1";
pyproject = true;
src = fetchPypi {
pname = "arxiv_mcp_server";
inherit version;
hash = "sha256-vxrKyq+uOgVYtWrvikcIidLra6n0GFlFvfKztrc7GH8=";
hash = "sha256-yGNetU7el6ZXsavD8uvO17OZtaPuYgzkxiVEk402GUs=";
};
build-system = [
python.pkgs.hatchling
python3.pkgs.hatchling
];
dependencies = with python.pkgs; [
dependencies = with python3.pkgs; [
aiofiles
aiohttp
anyio
@ -39,7 +36,7 @@ python.pkgs.buildPythonApplication rec {
uvicorn
];
optional-dependencies = with python.pkgs; {
optional-dependencies = with python3.pkgs; {
test = [
aioresponses
pytest
@ -51,7 +48,6 @@ python.pkgs.buildPythonApplication rec {
pythonRemoveDeps = [
"black"
"mcp"
];
pythonImportsCheck = [

View file

@ -11,19 +11,17 @@
rustPlatform.buildRustPackage rec {
pname = "baibot";
version = "1.19.3";
version = "1.14.3";
src = fetchFromGitHub {
owner = "etkecc";
repo = "baibot";
rev = "v${version}";
hash = "sha256-Fr1CvFocb/VAYQGykXXZ6CCfvC31bKB/tr1aoA4oIME=";
hash = "sha256-bFUijsvwUQhISjWVoVvoDXNSDPaWZTunqUfxfgaxclM=";
};
cargoHash = "sha256-CNEkge585bzUUPMHCSJ1CAH5wx3Wttq9I3A3oqfBzis=";
cargoBuildFlags = "--ignore-rust-version";
cargoTestFlags = "--ignore-rust-version";
useFetchCargoVendor = true;
cargoHash = "sha256-/7KSCVWuxTk7gKOYxE/uQ5T0BnYlDYWOvdFXEiU9mB0=";
nativeBuildInputs = [
pkg-config

View file

@ -6,13 +6,13 @@
python3.pkgs.buildPythonApplication rec {
pname = "blender-mcp";
version = "1.5.6";
version = "1.5.5";
pyproject = true;
src = fetchPypi {
pname = "blender_mcp";
inherit version;
hash = "sha256-9aBGQcMC1ustj3H1WgRFCha6fk+XSASIzcZr6Rrgif4=";
hash = "sha256-n8bYnrDVLe5zPvg8rbGgh5U5AeIm/DA6aPgmxS68d7g=";
};
build-system = [

View file

@ -11,20 +11,15 @@
cppman = pkgs.callPackage ./cppman { };
fetcher-mcp = pkgs.callPackage ./fetcher-mcp { };
freecad-mcp = pkgs.callPackage ./freecad-mcp { };
jirafeau = pkgs.callPackage ./jirafeau { };
jirafeau-cli = pkgs.callPackage ./jirafeau-cli { };
kicad-mcp = pkgs.callPackage ./kicad-mcp { };
mcpo = pkgs.callPackage ./mcpo { };
nerdlog = pkgs.callPackage ./nerdlog { };
pass2bw = pkgs.callPackage ./pass2bw { };
pyman = pkgs.callPackage ./pyman { };
quicknote = pkgs.callPackage ./quicknote { };
synapse_change_display_name = pkgs.callPackage ./synapse_change_display_name { };
synix-docs = pkgs.callPackage ./synix-docs { };
tinyfugue = pkgs.callPackage ./tinyfugue { };
trelis-gitingest-mcp = pkgs.callPackage ./trelis-gitingest-mcp { };
tunerstudio = pkgs.callPackage ./tunerstudio { };
# marker-pdf = pkgs.callPackage ./marker-pdf { }; # FIXME
# openmv-ide = pkgs.callPackage ./openmv-ide { }; # FIXME
}

View file

@ -12,9 +12,11 @@
let
revision = "1161";
chromium-headless-shell = playwright-driver.passthru.components."chromium-headless-shell".overrideAttrs (old: {
inherit revision;
});
chromium-headless-shell =
playwright-driver.passthru.components."chromium-headless-shell".overrideAttrs
(old: {
inherit revision;
});
browsers-headless-only = linkFarm "playwright-browsers-headless-only" [
{
@ -23,9 +25,9 @@ let
}
];
in
buildNpmPackage {
buildNpmPackage rec {
pname = "fetcher-mcp";
version = "0.3.9";
version = "0.3.6";
src = fetchFromGitHub {
owner = "jae-jae";
@ -60,6 +62,7 @@ buildNpmPackage {
description = "MCP server for fetch web page content using Playwright headless browser";
homepage = "https://github.com/jae-jae/fetcher-mcp";
license = lib.licenses.mit;
maintainers = with lib.maintainers; [ ];
mainProgram = "fetcher-mcp";
platforms = lib.platforms.all;
};

View file

@ -6,13 +6,13 @@
python3.pkgs.buildPythonApplication rec {
pname = "freecad-mcp";
version = "0.1.17";
version = "0.1.16";
pyproject = true;
src = fetchPypi {
pname = "freecad_mcp";
inherit version;
hash = "sha256-DugQWGZizIXO27cjzfyidNTyyahN1BTcDNTl4DHWqN0=";
hash = "sha256-eFt7UgJw56C2zY8Vznrg8L0a5nvP0xtiW1oJuJ/fL3Y=";
};
build-system = [

View file

@ -1,20 +0,0 @@
{
writeShellApplication,
coreutils,
curl,
...
}:
let
name = "jirafeau-cli";
text = builtins.readFile ./${name}.sh;
in
writeShellApplication {
inherit name text;
meta.mainProgram = name;
runtimeInputs = [
coreutils
curl
];
}

View file

@ -1,152 +0,0 @@
#!/bin/bash
# This script has been auto-generated by Jirafeau but you can still edit options below.
# Config begin
proxy='' # Or set JIRAFEAU_PROXY.
url='' # Or set JIRAFEAU_URL.
time='month' # Or set JIRAFEAU_TIME.
one_time='' # Or set JIRAFEAU_ONE_TIME.
upload_password='' # Or set JIRAFEAU_UPLOAD_PASSWD
# Config end
if [ -n "${JIRAFEAU_PROXY:-}" ]; then
proxy="$JIRAFEAU_PROXY"
fi
if [ -n "${JIRAFEAU_URL:-}" ]; then
url="$JIRAFEAU_URL"
fi
if [ -z "$url" ]; then
echo "Please set url in script parameters or export JIRAFEAU_URL"
fi
if [ -n "${JIRAFEAU_TIME:-}" ]; then
time="$JIRAFEAU_TIME"
fi
if [ -n "${JIRAFEAU_ONE_TIME:-}" ]; then
one_time='1'
fi
if [ -n "${JIRAFEAU_UPLOAD_PASSWD:-}" ]; then
upload_password="$JIRAFEAU_UPLOAD_PASSWD"
fi
if [ -z "${2:-}" ]; then
echo "Jirafeau Bash Script 4.7.1"
echo "--------------------------"
echo "Usage:"
echo " $0 OPTIONS"
echo
echo "Options:"
echo " $0 send FILE [PASSWORD]"
echo " $0 get URL [PASSWORD]"
echo " $0 delete URL"
echo
echo "Global variables to export:"
echo " JIRAFEAU_PROXY: Domain and port of proxy server, eg. »proxyserver.example.com:3128«"
echo " JIRAFEAU_URL : URI to Jirafeau installation with trailing slash, eg. »https://example.com/jirafeau/«"
echo " JIRAFEAU_TIME : expiration time, eg. »minute«, »hour«, »day«, »week«, fortnight, »month«, »quarter«, »year« or »none«"
echo " JIRAFEAU_ONE_TIME : self-destroy after first download, eg. »1« to enable or »« (empty) to disable"
echo " JIRAFEAU_UPLOAD_PASSWD : upload password"
exit 0
fi
proxy_args=()
if [ -n "$proxy" ]; then
proxy_args=(-x "$proxy")
fi
options=()
if [ -n "$one_time" ]; then
options+=(-F one_time_download=1)
fi
if [ -n "$upload_password" ]; then
options+=(-F "upload_password=$upload_password")
fi
password=''
if [ -n "${3:-}" ]; then
password="$3"
options+=(-F "key=$password")
fi
apipage='script.php'
downloadpage='f.php'
if [ "$1" == "send" ]; then
if [ ! -f "$2" ]; then
echo "File \"$2\" does not exist."
exit
fi
# Get result
res=$(curl -X POST --http1.0 "${proxy_args[@]}" "${options[@]}" \
-F "time=$time" \
-F "file=@$2" \
"${url}${apipage}")
if [[ "$res" == Error* ]]; then
echo "Error while uploading."
echo "$res"
exit
fi
# Not using head or tail to minimise command dependencies
code=$(cnt=0; echo "$res" | while read -r l; do
if [[ "$cnt" == "0" ]]; then
echo "$l"
fi
cnt=$(( cnt + 1 ))
done)
del_code=$(cnt=0; echo "$res" | while read -r l; do
if [[ "$cnt" == "1" ]]; then
echo "$l"
fi
cnt=$(( cnt + 1 ))
done)
key_code=$(cnt=0; echo "$res" | while read -r l; do
if [[ "$cnt" == "2" ]]; then
echo "$l"
fi
cnt=$(( cnt + 1 ))
done)
echo
echo "Download page:"
if [[ $key_code ]]; then
echo " ${url}${downloadpage}?h=$code&k=$key_code"
else
echo " ${url}${downloadpage}?h=$code"
fi
echo "Direct download:"
if [[ $key_code ]]; then
echo " ${url}${downloadpage}?h=$code&k=$key_code&d=1"
else
echo " ${url}${downloadpage}?h=$code&d=1"
fi
echo "Delete link:"
echo " ${url}${downloadpage}?h=$code&d=$del_code"
echo
echo "Download via API:"
if [[ $key_code ]]; then
echo " ${0} get ${url}${apipage}?h=$code&k=$key_code [PASSWORD]"
else
echo " ${0} get ${url}${apipage}?h=$code [PASSWORD]"
fi
echo "Delete via API:"
echo " ${0} delete \"${url}${downloadpage}?h=$code&d=$del_code\""
elif [ "$1" == "get" ]; then
if [ -z "$password" ]; then
curl "${proxy_args[@]}" -OJ "$2"
else
curl "${proxy_args[@]}" -OJ -X POST -F "key=$password" "$2"
fi
elif [ "$1" == "delete" ]; then
curl "${proxy_args[@]}" "$2" --data-raw "do_delete=1%2F" | grep "div class" | sed -e "s/<[^>]\+>//g"
fi

Some files were not shown because too many files have changed in this diff Show more