This commit is contained in:
commit
95a533c876
451 changed files with 18255 additions and 0 deletions
24
modules/nixos/amd/default.nix
Normal file
24
modules/nixos/amd/default.nix
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{ pkgs, ... }:
|
||||
|
||||
{
|
||||
boot.initrd.kernelModules = [ "amdgpu" ];
|
||||
|
||||
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;
|
||||
|
||||
systemd.packages = with pkgs; [ lact ];
|
||||
systemd.services.lactd.wantedBy = [ "multi-user.target" ];
|
||||
}
|
||||
28
modules/nixos/audio/default.nix
Normal file
28
modules/nixos/audio/default.nix
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (lib) mkDefault;
|
||||
in
|
||||
{
|
||||
services.pipewire = {
|
||||
enable = mkDefault true;
|
||||
wireplumber.enable = mkDefault true;
|
||||
alsa.enable = mkDefault true;
|
||||
pulse.enable = mkDefault true;
|
||||
jack.enable = mkDefault true;
|
||||
audio.enable = mkDefault true;
|
||||
};
|
||||
|
||||
services.pulseaudio.enable = false;
|
||||
|
||||
security.rtkit.enable = mkDefault config.services.pipewire.enable;
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
pulseaudioFull
|
||||
];
|
||||
}
|
||||
98
modules/nixos/baibot/default.nix
Normal file
98
modules/nixos/baibot/default.nix
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.services.baibot;
|
||||
homeDir = "/var/lib/baibot";
|
||||
|
||||
inherit (lib)
|
||||
mkEnableOption
|
||||
mkIf
|
||||
mkOption
|
||||
optional
|
||||
types
|
||||
;
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services.baibot = {
|
||||
enable = mkEnableOption "Baibot, a Matrix AI bot.";
|
||||
|
||||
configFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = "${homeDir}/config.yml";
|
||||
description = ''
|
||||
Path to the baibot configuration file. Use the template for reference:
|
||||
https://github.com/etkecc/baibot/blob/main/etc/app/config.yml.dist
|
||||
'';
|
||||
};
|
||||
|
||||
persistenceDataDirPath = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = "${homeDir}/data";
|
||||
description = ''
|
||||
Path to the directory where baibot will store its persistent data.
|
||||
'';
|
||||
};
|
||||
|
||||
environmentFile = lib.mkOption {
|
||||
description = ''
|
||||
Path to an environment file that is passed to the systemd service.
|
||||
'';
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
default = null;
|
||||
example = "/run/secrets/baibot";
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
type = types.nullOr types.package;
|
||||
default = null;
|
||||
description = ''
|
||||
The baibot package to use for the service. This must be set by the user,
|
||||
as there is no default baibot package available in Nixpkgs.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.package != null;
|
||||
message = "The baibot package is not specified. Please set the services.baibot.package option to a valid baibot package.";
|
||||
}
|
||||
];
|
||||
|
||||
users = {
|
||||
users.baibot = {
|
||||
isSystemUser = true;
|
||||
description = "Baibot system user";
|
||||
home = homeDir;
|
||||
createHome = true;
|
||||
group = "baibot";
|
||||
};
|
||||
groups.baibot = { };
|
||||
};
|
||||
|
||||
systemd.services.baibot = {
|
||||
description = "Baibot Service";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
environment = {
|
||||
BAIBOT_CONFIG_FILE_PATH = cfg.configFile;
|
||||
BAIBOT_PERSISTENCE_DATA_DIR_PATH = cfg.persistenceDataDirPath;
|
||||
};
|
||||
serviceConfig = {
|
||||
ExecStart = "${cfg.package}/bin/baibot";
|
||||
EnvironmentFile = optional (cfg.environmentFile != null) cfg.environmentFile;
|
||||
Restart = "always";
|
||||
User = "baibot";
|
||||
Group = "baibot";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
22
modules/nixos/bluetooth/default.nix
Normal file
22
modules/nixos/bluetooth/default.nix
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{ lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
inherit (lib) mkDefault;
|
||||
in
|
||||
{
|
||||
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
|
||||
bluez
|
||||
bluez-tools
|
||||
];
|
||||
|
||||
boot.kernelModules = [
|
||||
"btusb"
|
||||
"bluetooth"
|
||||
];
|
||||
}
|
||||
91
modules/nixos/cifsMount/default.nix
Normal file
91
modules/nixos/cifsMount/default.nix
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.cifsMount;
|
||||
in
|
||||
{
|
||||
|
||||
options.services.cifsMount = {
|
||||
enable = mkEnableOption "CIFS mounting of predefined remote directories.";
|
||||
|
||||
# List of predefined remote CIFS shares to mount
|
||||
remotes = mkOption {
|
||||
type = with types; listOf (attrsOf str);
|
||||
default = [ ];
|
||||
description = "List of predefined remote CIFS shares to mount.";
|
||||
example = [
|
||||
{
|
||||
# Example entry for a CIFS mount.
|
||||
host = "remotehost";
|
||||
shareName = "share";
|
||||
mountPoint = "/local/mountpoint";
|
||||
credentialsFile = "/path/to/credentials";
|
||||
user = "yourusername";
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
environment.systemPackages = with pkgs; [ cifs-utils ];
|
||||
|
||||
# Define /etc/fstab entries for each remote CIFS share
|
||||
fileSystems = fold (
|
||||
remote: fs:
|
||||
let
|
||||
mountOption = "credentials=${remote.credentialsFile},uid=${remote.user},gid=${remote.user},user,noauto";
|
||||
in
|
||||
fs
|
||||
// {
|
||||
"${toString remote.mountPoint}" = {
|
||||
device = "//${remote.host}/${remote.shareName}";
|
||||
fsType = "cifs";
|
||||
options = [
|
||||
(optionalString (remote.credentialsFile != null) "credentials=${remote.credentialsFile}")
|
||||
"uid=${remote.user}"
|
||||
"gid=${remote.user}"
|
||||
"user"
|
||||
"noauto"
|
||||
];
|
||||
};
|
||||
}
|
||||
) { } cfg.remotes;
|
||||
|
||||
# Create systemd user services for each remote CIFS share
|
||||
systemd.user.services = fold (
|
||||
remote: services:
|
||||
let
|
||||
serviceName = "cifs-mount-${remote.shareName}";
|
||||
in
|
||||
{
|
||||
${serviceName} = {
|
||||
description = "Mount remote share: //${remote.host}/${remote.shareName} to ${remote.mountPoint}";
|
||||
wantedBy = [ "graphical-session.target" ];
|
||||
after = [ "graphical-session.target" ];
|
||||
|
||||
# Service configuration to mount and unmount CIFS share
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
ExecStart = "mount //${remote.host}/${remote.shareName}";
|
||||
ExecStop = "umount //${remote.host}/${remote.shareName}";
|
||||
Restart = "on-failure";
|
||||
};
|
||||
};
|
||||
}
|
||||
// services
|
||||
) { } 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;
|
||||
};
|
||||
}
|
||||
14
modules/nixos/common/default.nix
Normal file
14
modules/nixos/common/default.nix
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
imports = [
|
||||
./environment.nix
|
||||
./htop.nix
|
||||
./nationalization.nix
|
||||
./networking.nix
|
||||
./nix.nix
|
||||
./sudo.nix
|
||||
./well-known.nix
|
||||
./zsh.nix
|
||||
|
||||
../../shared/common
|
||||
];
|
||||
}
|
||||
63
modules/nixos/common/environment.nix
Normal file
63
modules/nixos/common/environment.nix
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
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
|
||||
|
||||
(callPackage ../../../apps/rebuild { })
|
||||
]
|
||||
++ optionals (pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform) [
|
||||
pkgs.kitty.terminfo
|
||||
];
|
||||
|
||||
environment.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.
|
||||
environment.ldso32 = null;
|
||||
|
||||
boot.tmp.cleanOnBoot = mkDefault true;
|
||||
boot.initrd.systemd.enable = mkDefault (!config.boot.swraid.enable && !config.boot.isContainer);
|
||||
}
|
||||
8
modules/nixos/common/htop.nix
Normal file
8
modules/nixos/common/htop.nix
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
programs.htop = {
|
||||
enable = true;
|
||||
settings = {
|
||||
highlight_base_name = 1;
|
||||
};
|
||||
};
|
||||
}
|
||||
31
modules/nixos/common/nationalization.nix
Normal file
31
modules/nixos/common/nationalization.nix
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
{ lib, ... }:
|
||||
|
||||
let
|
||||
de = "de_DE.UTF-8";
|
||||
en = "en_US.UTF-8";
|
||||
|
||||
inherit (lib) mkDefault;
|
||||
in
|
||||
{
|
||||
i18n = {
|
||||
defaultLocale = mkDefault en;
|
||||
extraLocaleSettings = {
|
||||
LC_ADDRESS = mkDefault de;
|
||||
LC_IDENTIFICATION = mkDefault de;
|
||||
LC_MEASUREMENT = mkDefault de;
|
||||
LC_MONETARY = mkDefault de;
|
||||
LC_NAME = mkDefault de;
|
||||
LC_NUMERIC = mkDefault de;
|
||||
LC_PAPER = mkDefault de;
|
||||
LC_TELEPHONE = mkDefault de;
|
||||
LC_TIME = mkDefault en;
|
||||
};
|
||||
};
|
||||
|
||||
console = {
|
||||
font = mkDefault "Lat2-Terminus16";
|
||||
keyMap = mkDefault "de";
|
||||
};
|
||||
|
||||
time.timeZone = mkDefault "Europe/Berlin";
|
||||
}
|
||||
40
modules/nixos/common/networking.nix
Normal file
40
modules/nixos/common/networking.nix
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (lib) mkDefault;
|
||||
inherit (lib.utils) isNotEmptyStr;
|
||||
in
|
||||
{
|
||||
config = {
|
||||
assertions = [
|
||||
{
|
||||
assertion = isNotEmptyStr config.networking.domain;
|
||||
message = "synix/nixos/common: config.networking.domain cannot be empty.";
|
||||
}
|
||||
{
|
||||
assertion = isNotEmptyStr config.networking.hostName;
|
||||
message = "synix/nixos/common: config.networking.hostName cannot be empty.";
|
||||
}
|
||||
];
|
||||
|
||||
networking = {
|
||||
domain = mkDefault "${config.networking.hostName}.local";
|
||||
hostId = mkDefault "8425e349"; # same as NixOS install ISO and nixos-anywhere
|
||||
|
||||
# NetworkManager
|
||||
useDHCP = false;
|
||||
networkmanager = {
|
||||
enable = true;
|
||||
plugins = with pkgs; [
|
||||
networkmanager-openconnect
|
||||
networkmanager-openvpn
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
19
modules/nixos/common/nix.nix
Normal file
19
modules/nixos/common/nix.nix
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (lib) mkDefault;
|
||||
in
|
||||
{
|
||||
nix = {
|
||||
# use flakes
|
||||
channel.enable = mkDefault false;
|
||||
|
||||
# De-duplicate store paths using hardlinks except in containers
|
||||
# where the store is host-managed.
|
||||
optimise.automatic = mkDefault (!config.boot.isContainer);
|
||||
};
|
||||
}
|
||||
26
modules/nixos/common/sudo.nix
Normal file
26
modules/nixos/common/sudo.nix
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
{ config, ... }:
|
||||
|
||||
{
|
||||
security.sudo = {
|
||||
enable = true;
|
||||
execWheelOnly = true;
|
||||
extraConfig = ''
|
||||
Defaults lecture = never
|
||||
'';
|
||||
};
|
||||
|
||||
assertions =
|
||||
let
|
||||
validUsers = users: users == [ ] || users == [ "root" ];
|
||||
validGroups = groups: groups == [ ] || groups == [ "wheel" ];
|
||||
validUserGroups = builtins.all (
|
||||
r: validUsers (r.users or [ ]) && validGroups (r.groups or [ ])
|
||||
) config.security.sudo.extraRules;
|
||||
in
|
||||
[
|
||||
{
|
||||
assertion = config.security.sudo.execWheelOnly -> validUserGroups;
|
||||
message = "Some definitions in `security.sudo.extraRules` refer to users other than 'root' or groups other than 'wheel'. Disable `config.security.sudo.execWheelOnly`, or adjust the rules.";
|
||||
}
|
||||
];
|
||||
}
|
||||
17
modules/nixos/common/well-known.nix
Normal file
17
modules/nixos/common/well-known.nix
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
# avoid TOFU MITM
|
||||
programs.ssh.knownHosts = {
|
||||
"github.com".hostNames = [ "github.com" ];
|
||||
"github.com".publicKey =
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl";
|
||||
|
||||
"gitlab.com".hostNames = [ "gitlab.com" ];
|
||||
"gitlab.com".publicKey =
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjquxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf";
|
||||
|
||||
"git.sr.ht".hostNames = [ "git.sr.ht" ];
|
||||
"git.sr.ht".publicKey =
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMZvRd4EtM7R+IHVMWmDkVU3VLQTSwQDSAvW0t2Tkj60";
|
||||
};
|
||||
# TODO: add synix
|
||||
}
|
||||
26
modules/nixos/common/zsh.nix
Normal file
26
modules/nixos/common/zsh.nix
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
programs.zsh = {
|
||||
enable = true;
|
||||
syntaxHighlighting = {
|
||||
enable = true;
|
||||
highlighters = [
|
||||
"main"
|
||||
"brackets"
|
||||
"cursor"
|
||||
"pattern"
|
||||
];
|
||||
patterns = {
|
||||
"rm -rf" = "fg=white,bold,bg=red";
|
||||
"rm -fr" = "fg=white,bold,bg=red";
|
||||
};
|
||||
};
|
||||
autosuggestions = {
|
||||
enable = true;
|
||||
strategy = [
|
||||
"completion"
|
||||
"history"
|
||||
];
|
||||
};
|
||||
enableLsColors = true;
|
||||
};
|
||||
}
|
||||
125
modules/nixos/coturn/default.nix
Normal file
125
modules/nixos/coturn/default.nix
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
cfg = config.services.coturn;
|
||||
|
||||
inherit (lib)
|
||||
mkDefault
|
||||
mkEnableOption
|
||||
mkIf
|
||||
mkOption
|
||||
optionalString
|
||||
optionals
|
||||
types
|
||||
;
|
||||
in
|
||||
{
|
||||
options.services.coturn = {
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether to open the firewall for coturn.";
|
||||
};
|
||||
debug = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Enable debug logging for coturn.";
|
||||
};
|
||||
sops = mkEnableOption "SOPS integration";
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
services.coturn = {
|
||||
no-cli = mkDefault true;
|
||||
no-tcp-relay = mkDefault true;
|
||||
no-tls = mkDefault false;
|
||||
min-port = mkDefault 49000;
|
||||
max-port = mkDefault 50000;
|
||||
listening-port = mkDefault 3478;
|
||||
tls-listening-port = mkDefault 5349;
|
||||
|
||||
use-auth-secret = true;
|
||||
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";
|
||||
|
||||
extraConfig = ''
|
||||
# ban private IP ranges
|
||||
no-multicast-peers
|
||||
denied-peer-ip=0.0.0.0-0.255.255.255
|
||||
denied-peer-ip=10.0.0.0-10.255.255.255
|
||||
denied-peer-ip=100.64.0.0-100.127.255.255
|
||||
denied-peer-ip=127.0.0.0-127.255.255.255
|
||||
denied-peer-ip=169.254.0.0-169.254.255.255
|
||||
denied-peer-ip=172.16.0.0-172.31.255.255
|
||||
denied-peer-ip=192.0.0.0-192.0.0.255
|
||||
denied-peer-ip=192.0.2.0-192.0.2.255
|
||||
denied-peer-ip=192.88.99.0-192.88.99.255
|
||||
denied-peer-ip=192.168.0.0-192.168.255.255
|
||||
denied-peer-ip=198.18.0.0-198.19.255.255
|
||||
denied-peer-ip=198.51.100.0-198.51.100.255
|
||||
denied-peer-ip=203.0.113.0-203.0.113.255
|
||||
denied-peer-ip=240.0.0.0-255.255.255.255
|
||||
denied-peer-ip=::1
|
||||
denied-peer-ip=64:ff9b::-64:ff9b::ffff:ffff
|
||||
denied-peer-ip=::ffff:0.0.0.0-::ffff:255.255.255.255
|
||||
denied-peer-ip=100::-100::ffff:ffff:ffff:ffff
|
||||
denied-peer-ip=2001::-2001:1ff:ffff:ffff:ffff:ffff:ffff:ffff
|
||||
denied-peer-ip=2002::-2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff
|
||||
denied-peer-ip=fc00::-fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
|
||||
denied-peer-ip=fe80::-febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff
|
||||
''
|
||||
+ optionalString cfg.debug ''
|
||||
# debugging
|
||||
syslog
|
||||
verbose
|
||||
'';
|
||||
};
|
||||
|
||||
networking.firewall = mkIf cfg.openFirewall {
|
||||
allowedUDPPortRanges = [
|
||||
{
|
||||
from = cfg.min-port;
|
||||
to = cfg.max-port;
|
||||
}
|
||||
];
|
||||
allowedUDPPorts = [
|
||||
cfg.listening-port
|
||||
cfg.alt-listening-port
|
||||
]
|
||||
++ optionals (!cfg.no-tls) [
|
||||
cfg.tls-listening-port
|
||||
cfg.alt-tls-listening-port
|
||||
];
|
||||
allowedTCPPorts = [
|
||||
cfg.listening-port
|
||||
cfg.alt-listening-port
|
||||
]
|
||||
++ optionals (!cfg.no-tls) [
|
||||
cfg.tls-listening-port
|
||||
cfg.alt-tls-listening-port
|
||||
];
|
||||
};
|
||||
|
||||
security.acme.certs = mkIf (!cfg.no-tls) {
|
||||
${cfg.realm} = {
|
||||
postRun = "systemctl restart coturn.service";
|
||||
group = "turnserver";
|
||||
};
|
||||
};
|
||||
|
||||
sops = mkIf cfg.sops {
|
||||
secrets."coturn/static-auth-secret" = {
|
||||
owner = "turnserver";
|
||||
group = "turnserver";
|
||||
mode = "0400";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
36
modules/nixos/default.nix
Normal file
36
modules/nixos/default.nix
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
amd = import ./amd;
|
||||
audio = import ./audio;
|
||||
baibot = import ./baibot;
|
||||
bluetooth = import ./bluetooth;
|
||||
cifsMount = import ./cifsMount;
|
||||
common = import ./common;
|
||||
coturn = import ./coturn;
|
||||
device = import ./device;
|
||||
ftp-webserver = import ./ftp-webserver;
|
||||
headplane = import ./headplane;
|
||||
headscale = import ./headscale;
|
||||
hyprland = import ./hyprland;
|
||||
i2pd = import ./i2pd;
|
||||
jellyfin = import ./jellyfin;
|
||||
jirafeau = import ./jirafeau;
|
||||
mailserver = import ./mailserver;
|
||||
matrix-synapse = import ./matrix-synapse;
|
||||
maubot = import ./maubot;
|
||||
mcpo = import ./mcpo;
|
||||
miniflux = import ./miniflux;
|
||||
nginx = import ./nginx;
|
||||
normalUsers = import ./normalUsers;
|
||||
nvidia = import ./nvidia;
|
||||
ollama = import ./ollama;
|
||||
open-webui-oci = import ./open-webui-oci;
|
||||
openssh = import ./openssh;
|
||||
print-server = import ./print-server;
|
||||
radicale = import ./radicale;
|
||||
rss-bridge = import ./rss-bridge;
|
||||
sops = import ./sops;
|
||||
tailscale = import ./tailscale;
|
||||
virtualisation = import ./virtualisation;
|
||||
webPage = import ./webPage;
|
||||
windows-oci = import ./windows-oci;
|
||||
}
|
||||
6
modules/nixos/device/default.nix
Normal file
6
modules/nixos/device/default.nix
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
desktop = import ./desktop.nix;
|
||||
laptop = import ./laptop.nix;
|
||||
server = import ./server.nix;
|
||||
vm = import ./vm.nix;
|
||||
}
|
||||
8
modules/nixos/device/desktop.nix
Normal file
8
modules/nixos/device/desktop.nix
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
imports = [
|
||||
../audio
|
||||
];
|
||||
|
||||
# improve desktop responsiveness when updating the system
|
||||
nix.daemonCPUSchedPolicy = "idle";
|
||||
}
|
||||
33
modules/nixos/device/laptop.nix
Normal file
33
modules/nixos/device/laptop.nix
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
{ pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
inherit (lib) mkDefault;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
./desktop.nix
|
||||
../bluetooth
|
||||
];
|
||||
|
||||
services.tlp = {
|
||||
enable = mkDefault true;
|
||||
settings = {
|
||||
CPU_SCALING_GOVERNOR_ON_AC = mkDefault "performance";
|
||||
CPU_SCALING_GOVERNOR_ON_BAT = mkDefault "powersave";
|
||||
|
||||
CPU_ENERGY_PERF_POLICY_ON_BAT = mkDefault "power";
|
||||
CPU_ENERGY_PERF_POLICY_ON_AC = mkDefault "performance";
|
||||
|
||||
PLATFORM_PROFILE_ON_AC = mkDefault "performance";
|
||||
PLATFORM_PROFILE_ON_BAT = mkDefault "low-power";
|
||||
|
||||
START_CHARGE_THRESH_BAT0 = mkDefault 75;
|
||||
STOP_CHARGE_THRESH_BAT0 = mkDefault 80;
|
||||
};
|
||||
};
|
||||
|
||||
# avoid conflicts with tlp
|
||||
services.power-profiles-daemon.enable = mkDefault false;
|
||||
|
||||
environment.systemPackages = [ pkgs.powertop ];
|
||||
}
|
||||
69
modules/nixos/device/server.nix
Normal file
69
modules/nixos/device/server.nix
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (config.programs) neovim;
|
||||
inherit (lib) mkDefault mkIf;
|
||||
in
|
||||
{
|
||||
environment = {
|
||||
variables = {
|
||||
BROWSER = "echo";
|
||||
EDITOR = mkIf neovim.enable "nvim";
|
||||
VISUAL = mkIf neovim.enable "nvim";
|
||||
};
|
||||
shellAliases = {
|
||||
v = mkIf neovim.enable "nvim";
|
||||
};
|
||||
# do not install /lib/ld-linux.so.2 and /lib64/ld-linux-x86-64.so.2
|
||||
stub-ld.enable = mkDefault false;
|
||||
};
|
||||
|
||||
documentation = {
|
||||
enable = mkDefault false;
|
||||
nixos.enable = mkDefault false;
|
||||
doc.enable = mkDefault false;
|
||||
info.enable = mkDefault false;
|
||||
man.enable = mkDefault false;
|
||||
};
|
||||
|
||||
fonts.fontconfig.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;
|
||||
|
||||
programs.neovim = {
|
||||
enable = mkDefault true;
|
||||
defaultEditor = mkDefault true;
|
||||
vimAlias = mkDefault true;
|
||||
viAlias = mkDefault true;
|
||||
};
|
||||
|
||||
# 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";
|
||||
};
|
||||
}
|
||||
10
modules/nixos/device/vm.nix
Normal file
10
modules/nixos/device/vm.nix
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{ lib, ... }:
|
||||
|
||||
let
|
||||
inherit (lib) mkDefault;
|
||||
in
|
||||
{
|
||||
services.qemuGuest.enable = mkDefault true;
|
||||
services.spice-vdagentd.enable = mkDefault true;
|
||||
services.spice-webdavd.enable = mkDefault true;
|
||||
}
|
||||
54
modules/nixos/ftp-webserver/default.nix
Normal file
54
modules/nixos/ftp-webserver/default.nix
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
cfg = config.services.ftp-webserver;
|
||||
domain = config.networking.domain;
|
||||
fqdn = if (cfg.subdomain != "") then "${cfg.subdomain}.${domain}" else domain;
|
||||
nginx = config.services.nginx;
|
||||
|
||||
inherit (lib)
|
||||
mkEnableOption
|
||||
mkIf
|
||||
mkOption
|
||||
types
|
||||
;
|
||||
in
|
||||
{
|
||||
options.services.ftp-webserver = {
|
||||
enable = mkEnableOption "FTP webserver.";
|
||||
subdomain = mkOption {
|
||||
type = types.str;
|
||||
default = "ftp";
|
||||
description = "Subdomain for Nginx virtual host. Leave empty for root domain.";
|
||||
};
|
||||
forceSSL = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Force SSL for Nginx virtual host.";
|
||||
};
|
||||
root = mkOption {
|
||||
type = types.str;
|
||||
default = "/srv/www";
|
||||
description = "Root directory for the FTP webserver.";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
services.nginx.virtualHosts."${fqdn}" = {
|
||||
root = cfg.root;
|
||||
locations."/" = {
|
||||
extraConfig = ''
|
||||
autoindex on;
|
||||
autoindex_exact_size off;
|
||||
autoindex_localtime on;
|
||||
'';
|
||||
};
|
||||
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";
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [ "d ${cfg.root} 0755 ${nginx.user} ${nginx.group}" ];
|
||||
};
|
||||
}
|
||||
78
modules/nixos/headplane/default.nix
Normal file
78
modules/nixos/headplane/default.nix
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
{
|
||||
inputs,
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.services.headplane;
|
||||
domain = config.networking.domain;
|
||||
subdomain = cfg.reverseProxy.subdomain;
|
||||
fqdn = if (cfg.reverseProxy.enable && subdomain != "") then "${subdomain}.${domain}" else domain;
|
||||
headscale = config.services.headscale;
|
||||
|
||||
inherit (lib)
|
||||
mkDefault
|
||||
mkIf
|
||||
;
|
||||
|
||||
inherit (lib.utils)
|
||||
mkReverseProxyOption
|
||||
mkVirtualHost
|
||||
;
|
||||
in
|
||||
{
|
||||
imports = [ inputs.headplane.nixosModules.headplane ];
|
||||
|
||||
options.services.headplane = {
|
||||
reverseProxy = mkReverseProxyOption "Headplane" "hp";
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
nixpkgs.overlays = [
|
||||
inputs.headplane.overlays.default
|
||||
];
|
||||
|
||||
services.headplane = {
|
||||
settings = {
|
||||
server = {
|
||||
host = mkDefault (if cfg.reverseProxy.enable then "127.0.0.1" else "0.0.0.0");
|
||||
port = mkDefault 3000;
|
||||
cookie_secret_path = config.sops.secrets."headplane/cookie_secret".path;
|
||||
};
|
||||
headscale = {
|
||||
url = "http://127.0.0.1:${toString headscale.port}";
|
||||
public_url = headscale.settings.server_url;
|
||||
config_path = "/etc/headscale/config.yaml";
|
||||
};
|
||||
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 {
|
||||
port = cfg.settings.server.port;
|
||||
ssl = cfg.reverseProxy.forceSSL;
|
||||
};
|
||||
};
|
||||
|
||||
sops.secrets =
|
||||
let
|
||||
owner = headscale.user;
|
||||
group = headscale.group;
|
||||
mode = "0400";
|
||||
in
|
||||
{
|
||||
"headplane/cookie_secret" = {
|
||||
inherit owner group mode;
|
||||
};
|
||||
"headplane/agent_pre_authkey" = {
|
||||
inherit owner group mode;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
17
modules/nixos/headscale/acl.hujson
Normal file
17
modules/nixos/headscale/acl.hujson
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"acls": [
|
||||
{
|
||||
"action": "accept",
|
||||
"src": ["*"],
|
||||
"dst": ["*:*"]
|
||||
}
|
||||
],
|
||||
"ssh": [
|
||||
{
|
||||
"action": "accept",
|
||||
"src": ["autogroup:member"],
|
||||
"dst": ["autogroup:member"],
|
||||
"users": ["autogroup:nonroot", "root"]
|
||||
}
|
||||
]
|
||||
}
|
||||
105
modules/nixos/headscale/default.nix
Normal file
105
modules/nixos/headscale/default.nix
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.services.headscale;
|
||||
domain = config.networking.domain;
|
||||
subdomain = cfg.reverseProxy.subdomain;
|
||||
fqdn = if (cfg.reverseProxy.enable && subdomain != "") then "${subdomain}.${domain}" else domain;
|
||||
acl = "headscale/acl.hujson";
|
||||
|
||||
inherit (lib)
|
||||
mkDefault
|
||||
mkIf
|
||||
mkOption
|
||||
optional
|
||||
optionals
|
||||
types
|
||||
;
|
||||
|
||||
inherit (lib.utils)
|
||||
mkReverseProxyOption
|
||||
mkUrl
|
||||
mkVirtualHost
|
||||
;
|
||||
in
|
||||
{
|
||||
options.services.headscale = {
|
||||
reverseProxy = mkReverseProxyOption "Headscale" "hs";
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether to automatically open firewall ports. TCP: 80, 443; UDP: 3478.";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = !cfg.settings.derp.server.enable || cfg.reverseProxy.forceSSL;
|
||||
message = "synix/nixos/headscale: DERP requires TLS";
|
||||
}
|
||||
{
|
||||
assertion = fqdn != cfg.settings.dns.base_domain;
|
||||
message = "synix/nixos/headscale: `settings.server_url` must be different from `settings.dns.base_domain`";
|
||||
}
|
||||
{
|
||||
assertion = !cfg.settings.dns.override_local_dns || cfg.settings.dns.nameservers.global != [ ];
|
||||
message = "synix/nixos/headscale: `settings.dns.nameservers.global` must be set when `settings.dns.override_local_dns` is true";
|
||||
}
|
||||
];
|
||||
|
||||
environment.etc.${acl} = {
|
||||
inherit (config.services.headscale) user group;
|
||||
source = ./acl.hujson;
|
||||
};
|
||||
|
||||
environment.shellAliases = {
|
||||
hs = "${cfg.package}/bin/headscale";
|
||||
};
|
||||
|
||||
services.headscale = {
|
||||
address = mkDefault (if cfg.reverseProxy.enable then "127.0.0.1" else "0.0.0.0");
|
||||
port = mkDefault 8077;
|
||||
settings = {
|
||||
policy.path = "/etc/${acl}";
|
||||
database.type = "sqlite"; # postgres is highly discouraged as it is only supported for legacy reasons
|
||||
server_url = mkUrl {
|
||||
inherit fqdn;
|
||||
ssl = with cfg.reverseProxy; enable && forceSSL;
|
||||
};
|
||||
derp.server.enable = cfg.reverseProxy.forceSSL;
|
||||
dns = {
|
||||
magic_dns = mkDefault true;
|
||||
base_domain = mkDefault "tail";
|
||||
search_domains = [ cfg.settings.dns.base_domain ];
|
||||
override_local_dns = mkDefault true;
|
||||
nameservers.global = optionals cfg.settings.dns.override_local_dns [
|
||||
"1.1.1.1"
|
||||
"1.0.0.1"
|
||||
"2606:4700:4700::1111"
|
||||
"2606:4700:4700::1001"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts = mkIf cfg.reverseProxy.enable {
|
||||
"${fqdn}" = mkVirtualHost {
|
||||
inherit (cfg) address port;
|
||||
ssl = cfg.reverseProxy.forceSSL;
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall = mkIf cfg.openFirewall {
|
||||
allowedTCPPorts = [
|
||||
80
|
||||
443
|
||||
];
|
||||
allowedUDPPorts = optional cfg.settings.derp.server.enable 3478;
|
||||
};
|
||||
};
|
||||
}
|
||||
27
modules/nixos/hyprland/default.nix
Normal file
27
modules/nixos/hyprland/default.nix
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
{ pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
inherit (lib) mkDefault;
|
||||
in
|
||||
{
|
||||
programs.hyprland.enable = mkDefault true;
|
||||
|
||||
programs.dconf.enable = true; # fixes nixvim hm module
|
||||
|
||||
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;
|
||||
}
|
||||
48
modules/nixos/i2pd/default.nix
Normal file
48
modules/nixos/i2pd/default.nix
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# TODO: HM config for i2p profile in LibreWolf
|
||||
|
||||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
cfg = config.services.i2pd;
|
||||
|
||||
inherit (lib)
|
||||
mkDefault
|
||||
mkIf
|
||||
mkOption
|
||||
optional
|
||||
types
|
||||
;
|
||||
in
|
||||
{
|
||||
options.services.i2pd = {
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether to open the necessary firewall ports for enabled protoclos of i2pd.";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
services.i2pd = {
|
||||
address = mkDefault "127.0.0.1";
|
||||
proto = {
|
||||
http.enable = mkDefault true;
|
||||
socksProxy.enable = mkDefault true;
|
||||
httpProxy.enable = mkDefault true;
|
||||
sam.enable = mkDefault true;
|
||||
i2cp.enable = mkDefault true;
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall (
|
||||
with cfg.proto;
|
||||
optional bob.enable bob.port
|
||||
++ optional http.enable http.port
|
||||
++ optional httpProxy.enable httpProxy.port
|
||||
++ optional i2cp.enable i2cp.port
|
||||
++ optional i2pControl.enable i2pControl.port
|
||||
++ optional sam.enable sam.port
|
||||
++ optional socksProxy.enable socksProxy.port
|
||||
);
|
||||
};
|
||||
}
|
||||
66
modules/nixos/jellyfin/default.nix
Normal file
66
modules/nixos/jellyfin/default.nix
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.services.jellyfin;
|
||||
domain = config.networking.domain;
|
||||
subdomain = cfg.reverseProxy.subdomain;
|
||||
fqdn = if (cfg.reverseProxy.enable && subdomain != "") then "${subdomain}.${domain}" else domain;
|
||||
|
||||
inherit (lib)
|
||||
mkDefault
|
||||
mkIf
|
||||
mkOption
|
||||
types
|
||||
;
|
||||
|
||||
inherit (lib.utils)
|
||||
mkReverseProxyOption
|
||||
mkVirtualHost
|
||||
;
|
||||
in
|
||||
{
|
||||
options.services.jellyfin = {
|
||||
reverseProxy = mkReverseProxyOption "Jellyfin" "jf";
|
||||
libraries = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [
|
||||
"movies"
|
||||
"music"
|
||||
"shows"
|
||||
];
|
||||
description = "A list of library names. Directories for these will be created under ${cfg.dataDir}/libraries.";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
services.jellyfin = {
|
||||
openFirewall = mkDefault false;
|
||||
};
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
jellyfin-web
|
||||
jellyfin-ffmpeg
|
||||
];
|
||||
|
||||
systemd.tmpfiles.rules =
|
||||
(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} -"
|
||||
];
|
||||
|
||||
services.nginx.virtualHosts = mkIf cfg.reverseProxy.enable {
|
||||
"${fqdn}" = mkVirtualHost {
|
||||
port = 8096;
|
||||
ssl = cfg.reverseProxy.forceSSL;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
44
modules/nixos/jirafeau/default.nix
Normal file
44
modules/nixos/jirafeau/default.nix
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
cfg = config.services.jirafeau;
|
||||
domain = config.networking.domain;
|
||||
subdomain = 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.jirafeau = {
|
||||
reverseProxy = mkReverseProxyOption "Jirafeau" "share";
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
services.jirafeau = {
|
||||
hostName = fqdn;
|
||||
extraConfig = mkDefault ''
|
||||
$cfg['style'] = 'dark-courgette';
|
||||
$cfg['maximal_upload_size'] = 4096;
|
||||
'';
|
||||
nginxConfig = {
|
||||
enableACME = if cfg.reverseProxy.enable then cfg.reverseProxy.forceSSL else mkDefault false;
|
||||
forceSSL = if cfg.reverseProxy.enable then cfg.reverseProxy.forceSSL else mkDefault false;
|
||||
listenAddresses = mkDefault [ "0.0.0.0" ]; # FIXME: 127.0.0.1 does not work
|
||||
serverName = fqdn;
|
||||
sslCertificate =
|
||||
mkIf (with cfg.reverseProxy; enable && forceSSL)
|
||||
"${config.security.acme.certs."${fqdn}".directory}/cert.pem";
|
||||
sslCertificateKey =
|
||||
mkIf (with cfg.reverseProxy; enable && forceSSL)
|
||||
"${config.security.acme.certs."${fqdn}".directory}/key.pem";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
104
modules/nixos/mailserver/default.nix
Normal file
104
modules/nixos/mailserver/default.nix
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
{
|
||||
inputs,
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.mailserver;
|
||||
domain = config.networking.domain;
|
||||
fqdn = "${cfg.subdomain}.${domain}";
|
||||
|
||||
inherit (lib)
|
||||
mapAttrs'
|
||||
mkDefault
|
||||
mkIf
|
||||
mkOption
|
||||
nameValuePair
|
||||
types
|
||||
;
|
||||
in
|
||||
{
|
||||
imports = [ inputs.nixos-mailserver.nixosModules.mailserver ];
|
||||
|
||||
options.mailserver = {
|
||||
subdomain = mkOption {
|
||||
type = types.str;
|
||||
default = "mail";
|
||||
description = "Subdomain for rDNS";
|
||||
};
|
||||
accounts = mkOption {
|
||||
type = types.attrsOf (
|
||||
types.submodule {
|
||||
options = {
|
||||
aliases = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
description = "A list of aliases of this account. `@domain` will be appended automatically.";
|
||||
};
|
||||
sendOnly = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Specifies if the account should be a send-only account.";
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
default = { };
|
||||
description = ''
|
||||
This options wraps `loginAccounts`.
|
||||
`loginAccounts.<attr-name>.name` will be automatically set to `<attr-name>@<domain>`.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.subdomain != "";
|
||||
message = "synix/nixos/mailserver: config.mailserver.subdomain cannot be empty.";
|
||||
}
|
||||
];
|
||||
|
||||
mailserver = {
|
||||
inherit fqdn;
|
||||
|
||||
domains = mkDefault [ domain ];
|
||||
certificateScheme = mkDefault "acme-nginx";
|
||||
stateVersion = mkDefault 1;
|
||||
|
||||
loginAccounts = mapAttrs' (
|
||||
user: accConf:
|
||||
nameValuePair "${user}@${domain}" {
|
||||
name = "${user}@${domain}";
|
||||
aliases = map (alias: "${alias}@${domain}") (accConf.aliases or [ ]);
|
||||
sendOnly = accConf.sendOnly;
|
||||
quota = mkDefault "5G";
|
||||
hashedPasswordFile = config.sops.secrets."mailserver/accounts/${user}".path;
|
||||
}
|
||||
) cfg.accounts;
|
||||
};
|
||||
|
||||
security.acme = {
|
||||
acceptTerms = true;
|
||||
defaults.email = "postmaster@${domain}";
|
||||
defaults.webroot = "/var/lib/acme/acme-challenge";
|
||||
};
|
||||
|
||||
environment.systemPackages = [ pkgs.mailutils ];
|
||||
|
||||
sops = {
|
||||
secrets = mapAttrs' (
|
||||
user: _config:
|
||||
nameValuePair "mailserver/accounts/${user}" {
|
||||
restartUnits = [
|
||||
"postfix.service"
|
||||
"dovecot.service"
|
||||
];
|
||||
}
|
||||
) cfg.accounts;
|
||||
};
|
||||
};
|
||||
}
|
||||
151
modules/nixos/matrix-synapse/bridges.nix
Normal file
151
modules/nixos/matrix-synapse/bridges.nix
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.services.matrix-synapse;
|
||||
|
||||
mkMautrixBridgeOptions = name: pkgName: {
|
||||
enable = mkEnableOption "Mautrix-${name} for your Matrix-Synapse instance.";
|
||||
package = mkPackageOption pkgs pkgName { };
|
||||
admin = mkOption {
|
||||
type = types.str;
|
||||
description = "The user to give admin permissions to.";
|
||||
example = "@admin:example.com";
|
||||
};
|
||||
};
|
||||
|
||||
mkMautrixBridge = name: port: {
|
||||
environment.systemPackages = [ cfg.bridges.${name}.package ];
|
||||
|
||||
services."mautrix-${name}" = {
|
||||
enable = true;
|
||||
package = cfg.bridges.${name}.package;
|
||||
environmentFile = mkIf cfg.sops config.sops.templates."mautrix-${name}/env-file".path;
|
||||
settings = {
|
||||
bridge = {
|
||||
permissions = {
|
||||
"*" = "relay";
|
||||
"${cfg.settings.server_name}" = "user";
|
||||
"${cfg.bridges.${name}.admin}" = "admin";
|
||||
};
|
||||
};
|
||||
homeserver = {
|
||||
address = "http://localhost:${toString cfg.port}";
|
||||
domain = cfg.settings.server_name;
|
||||
};
|
||||
appservice = {
|
||||
address = "http://localhost:${toString port}";
|
||||
public_address = cfg.settings.public_baseurl;
|
||||
hostname = "localhost";
|
||||
inherit port;
|
||||
};
|
||||
provisioning.shared_secret = "$MAUTRIX_${toUpper name}_PROVISIONING_SHARED_SECRET";
|
||||
public_media = {
|
||||
enabled = false;
|
||||
signing_key = "$MAUTRIX_${toUpper name}_PUBLIC_MEDIA_SIGNING_KEY";
|
||||
};
|
||||
direct_media = {
|
||||
enabled = false;
|
||||
server_key = "$MAUTRIX_${toUpper name}_DIRECT_MEDIA_SERVER_KEY";
|
||||
};
|
||||
backfill = {
|
||||
enabled = true;
|
||||
};
|
||||
encryption = {
|
||||
allow = true;
|
||||
default = true;
|
||||
require = false;
|
||||
pickle_key = "$MAUTRIX_${toUpper name}_ENCRYPTION_PICKLE_KEY";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
sops = mkIf cfg.sops (
|
||||
let
|
||||
owner = "mautrix-${name}";
|
||||
group = "mautrix-${name}";
|
||||
mode = "0400";
|
||||
in
|
||||
{
|
||||
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}_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"
|
||||
}
|
||||
'';
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
inherit (lib)
|
||||
mkEnableOption
|
||||
mkIf
|
||||
mkMerge
|
||||
mkOption
|
||||
mkPackageOption
|
||||
toUpper
|
||||
types
|
||||
;
|
||||
|
||||
inherit (builtins) toString;
|
||||
in
|
||||
{
|
||||
options.services.matrix-synapse = {
|
||||
bridges = {
|
||||
whatsapp = mkMautrixBridgeOptions "WhatsApp" "mautrix-whatsapp";
|
||||
signal = mkMautrixBridgeOptions "Signal" "mautrix-signal";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkMerge [
|
||||
(mkIf cfg.bridges.whatsapp.enable (mkMautrixBridge "whatsapp" 29318))
|
||||
(mkIf cfg.bridges.whatsapp.enable {
|
||||
services.mautrix-whatsapp = {
|
||||
settings = {
|
||||
network = {
|
||||
displayname_template = "{{or .BusinessName .PushName .Phone}} (WA)";
|
||||
history_sync.request_full_sync = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
})
|
||||
(mkIf cfg.bridges.signal.enable (mkMautrixBridge "signal" 29328))
|
||||
(mkIf cfg.bridges.signal.enable {
|
||||
services.mautrix-signal = {
|
||||
settings = {
|
||||
network = {
|
||||
displayname_template = "{{or .ProfileName .PhoneNumber \"Unknown user\" }} (S)";
|
||||
};
|
||||
};
|
||||
};
|
||||
})
|
||||
];
|
||||
}
|
||||
189
modules/nixos/matrix-synapse/default.nix
Normal file
189
modules/nixos/matrix-synapse/default.nix
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.services.matrix-synapse;
|
||||
baseUrl = "https://${cfg.settings.server_name}";
|
||||
|
||||
baseClientConfig = {
|
||||
"m.homeserver".base_url = baseUrl;
|
||||
"m.identity_server".base_url = "https://vector.im";
|
||||
};
|
||||
|
||||
livekitConfig = optionalAttrs config.services.livekit.enable {
|
||||
"org.matrix.msc3575.proxy".url = baseUrl;
|
||||
"org.matrix.msc4143.rtc_foci" = [
|
||||
{
|
||||
type = "livekit";
|
||||
livekit_service_url = baseUrl + "/livekit/jwt";
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
clientConfig = baseClientConfig // livekitConfig;
|
||||
|
||||
serverConfig."m.server" = "${cfg.settings.server_name}:443";
|
||||
|
||||
mkWellKnown = data: ''
|
||||
default_type application/json;
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
return 200 '${builtins.toJSON data}';
|
||||
'';
|
||||
|
||||
inherit (lib)
|
||||
mkEnableOption
|
||||
mkIf
|
||||
mkMerge
|
||||
mkOption
|
||||
optionalAttrs
|
||||
types
|
||||
;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
./bridges.nix
|
||||
./livekit.nix
|
||||
];
|
||||
|
||||
options.services.matrix-synapse = {
|
||||
port = mkOption {
|
||||
type = types.nullOr types.port;
|
||||
default = 8008;
|
||||
description = ''
|
||||
The port to listen for HTTP(S) requests on (will be applied to a listener).
|
||||
'';
|
||||
};
|
||||
coturn = {
|
||||
enable = mkEnableOption "Coturn integration for Matrix Synapse.";
|
||||
realm = mkOption {
|
||||
type = types.str;
|
||||
default = "turn.${config.networking.domain}";
|
||||
description = "Realm for the coturn server used by Matrix Synapse.";
|
||||
};
|
||||
listening-port = mkOption {
|
||||
type = types.port;
|
||||
default = 3478;
|
||||
};
|
||||
tls-listening-port = mkOption {
|
||||
type = types.port;
|
||||
default = 5349;
|
||||
};
|
||||
alt-listening-port = mkOption {
|
||||
type = types.port;
|
||||
default = 3479;
|
||||
};
|
||||
alt-tls-listening-port = mkOption {
|
||||
type = types.port;
|
||||
default = 5350;
|
||||
};
|
||||
};
|
||||
sops = mkEnableOption "SOPS integration";
|
||||
};
|
||||
|
||||
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.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 {
|
||||
secrets = mkMerge [
|
||||
{
|
||||
"matrix/registration-shared-secret" = {
|
||||
owner = "matrix-synapse";
|
||||
group = "matrix-synapse";
|
||||
mode = "0400";
|
||||
};
|
||||
}
|
||||
(mkIf cfg.coturn.enable {
|
||||
"coturn/static-auth-secret" = {
|
||||
owner = "turnserver";
|
||||
group = "turnserver";
|
||||
mode = "0400";
|
||||
};
|
||||
})
|
||||
];
|
||||
templates = mkIf cfg.coturn.enable {
|
||||
"coturn/static-auth-secret.env" = {
|
||||
owner = "matrix-synapse";
|
||||
group = "matrix-synapse";
|
||||
mode = "0400";
|
||||
content = ''
|
||||
static-auth-secret=${config.sops.placeholder."coturn/static-auth-secret"}
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
61
modules/nixos/matrix-synapse/livekit.nix
Normal file
61
modules/nixos/matrix-synapse/livekit.nix
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.services.matrix-synapse;
|
||||
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.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 = ''
|
||||
API Secret: ${config.sops.placeholder."livekit/key"}
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
110
modules/nixos/maubot/default.nix
Normal file
110
modules/nixos/maubot/default.nix
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
cfg = config.services.maubot;
|
||||
user = config.users.users.maubot;
|
||||
synapse = config.services.matrix-synapse;
|
||||
|
||||
inherit (lib)
|
||||
concatLines
|
||||
mkEnableOption
|
||||
mkIf
|
||||
mkOption
|
||||
optionalString
|
||||
types
|
||||
;
|
||||
inherit (builtins) toString listToAttrs;
|
||||
in
|
||||
{
|
||||
options.services.maubot = {
|
||||
admins = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
example = [
|
||||
"alice"
|
||||
"bob"
|
||||
];
|
||||
description = "List of admin users for Maubot. Each admin must have a corresponding entry in the SOPS file under 'maubot/admins/<admin>' containing their password";
|
||||
};
|
||||
sops = mkEnableOption "SOPS integration";
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
services.maubot = {
|
||||
extraConfigFile = mkIf cfg.sops config.sops.templates."maubot/extra-config-file".path;
|
||||
settings = {
|
||||
server = {
|
||||
port = 29316;
|
||||
public_url = synapse.settings.public_baseurl;
|
||||
};
|
||||
plugin_directories = with user; {
|
||||
upload = home + "/plugins";
|
||||
load = [ (home + "/plugins") ];
|
||||
trash = home + "/trash";
|
||||
};
|
||||
plugin_databases = with user; {
|
||||
sqlite = home + "/plugins";
|
||||
};
|
||||
# FIXME: ValueError: dictionary doesn't specify a version
|
||||
# logging = with user; {
|
||||
# handlers.file.filename = home + "/maubot.log";
|
||||
# };
|
||||
};
|
||||
};
|
||||
|
||||
environment.systemPackages = [
|
||||
cfg.package
|
||||
];
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d ${cfg.dataDir} 0755 ${user.name} ${user.group} -"
|
||||
"d ${cfg.settings.plugin_directories.upload} 0755 ${user.name} ${user.group} -"
|
||||
"d ${cfg.settings.plugin_directories.trash} 0755 ${user.name} ${user.group} -"
|
||||
];
|
||||
|
||||
services.nginx.virtualHosts."${synapse.settings.server_name}".locations = {
|
||||
"^~ /_matrix/maubot/" = {
|
||||
proxyPass = with cfg.settings.server; "http://${hostname}:${toString port}";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
"^~ /_matrix/maubot/v1/logs" = {
|
||||
proxyPass = with cfg.settings.server; "http://${hostname}:${toString port}";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
};
|
||||
|
||||
sops = mkIf cfg.sops (
|
||||
let
|
||||
owner = user.name;
|
||||
group = user.group;
|
||||
mode = "0400";
|
||||
in
|
||||
{
|
||||
secrets = listToAttrs (
|
||||
map (admin: {
|
||||
name = "maubot/admins/${admin}";
|
||||
value = { inherit owner group mode; };
|
||||
}) cfg.admins
|
||||
);
|
||||
templates."maubot/extra-config-file" = {
|
||||
inherit owner group mode;
|
||||
content = ''
|
||||
homeservers:
|
||||
${synapse.settings.server_name}:
|
||||
url: http://127.0.0.1:${toString synapse.port}
|
||||
secret: ${config.sops.placeholder."matrix/registration-shared-secret"}
|
||||
''
|
||||
+ optionalString (cfg.admins != [ ]) (
|
||||
''
|
||||
admins:
|
||||
''
|
||||
+ concatLines (
|
||||
map (admin: " ${admin}: ${config.sops.placeholder."maubot/admins/${admin}"}") cfg.admins
|
||||
)
|
||||
);
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
}
|
||||
111
modules/nixos/mcpo/default.nix
Normal file
111
modules/nixos/mcpo/default.nix
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.services.mcpo;
|
||||
|
||||
configFile = pkgs.writeText "mcpo-config.json" (builtins.toJSON cfg.settings);
|
||||
|
||||
inherit (lib)
|
||||
getExe
|
||||
mkEnableOption
|
||||
mkIf
|
||||
mkOption
|
||||
types
|
||||
;
|
||||
in
|
||||
{
|
||||
options.services.mcpo = {
|
||||
enable = mkEnableOption "mcpo, an MCP-to-OpenAPI proxy server.";
|
||||
|
||||
package = mkOption {
|
||||
type = types.nullOr types.package;
|
||||
description = "The package to use for mcpo. You have to specify this manually.";
|
||||
default = null;
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
description = "The user the mcpo service will run as.";
|
||||
default = "mcpo";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
description = "The group the mcpo service will run as.";
|
||||
default = "mcpo";
|
||||
};
|
||||
|
||||
workDir = mkOption {
|
||||
type = types.str;
|
||||
description = "The working directory for the mcpo service.";
|
||||
default = "/var/lib/mcpo";
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
type = types.attrs;
|
||||
description = "A set of attributes that will be translated into the JSON configuration file for mcpo. It follows the Claude Desktop format.";
|
||||
default = { };
|
||||
example = {
|
||||
mcpServers = {
|
||||
memory = {
|
||||
command = "npx";
|
||||
args = [
|
||||
"-y"
|
||||
"@modelcontextprotocol/server-memory"
|
||||
];
|
||||
};
|
||||
time = {
|
||||
command = "uvx";
|
||||
args = [
|
||||
"mcp-server-time"
|
||||
"--local-timezone=America/New_York"
|
||||
];
|
||||
};
|
||||
mcp_sse = {
|
||||
type = "sse";
|
||||
url = "http://127.0.0.1:8001/sse";
|
||||
headers = {
|
||||
Authorization = "Bearer token";
|
||||
X-Custom-Header = "value";
|
||||
};
|
||||
};
|
||||
mcp_streamable_http = {
|
||||
type = "streamable_http";
|
||||
url = "http://127.0.0.1:8002/mcp";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.tmpfiles.rules = [
|
||||
"d ${cfg.workDir} - ${cfg.user} ${cfg.group} - -"
|
||||
];
|
||||
|
||||
users.users."${cfg.user}" = {
|
||||
isSystemUser = true;
|
||||
group = cfg.group;
|
||||
};
|
||||
|
||||
users.groups."${cfg.group}" = { };
|
||||
|
||||
systemd.services.mcpo = {
|
||||
description = "Service for mcpo, an MCP-to-OpenAPI proxy server.";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${getExe cfg.package} --config ${configFile}";
|
||||
Restart = "on-failure";
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
WorkingDirectory = cfg.workDir;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
57
modules/nixos/miniflux/default.nix
Normal file
57
modules/nixos/miniflux/default.nix
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.services.miniflux;
|
||||
domain = config.networking.domain;
|
||||
subdomain = cfg.reverseProxy.subdomain;
|
||||
fqdn = if (cfg.reverseProxy.enable && subdomain != "") then "${subdomain}.${domain}" else domain;
|
||||
port = 8085;
|
||||
|
||||
inherit (lib)
|
||||
mkDefault
|
||||
mkIf
|
||||
;
|
||||
|
||||
inherit (lib.utils)
|
||||
mkReverseProxyOption
|
||||
mkVirtualHost
|
||||
;
|
||||
in
|
||||
{
|
||||
options.services.miniflux = {
|
||||
reverseProxy = mkReverseProxyOption "Miniflux" "rss";
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
services.miniflux = {
|
||||
adminCredentialsFile = config.sops.templates."miniflux/admin-credentials".path;
|
||||
createDatabaseLocally = mkDefault true;
|
||||
config = {
|
||||
ADMIN_USERNAME = mkDefault "admin";
|
||||
CREATE_ADMIN = mkDefault 1;
|
||||
PORT = mkDefault port; # overrides LISTEN_ADDR to "0.0.0.0:${PORT}"
|
||||
};
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts = mkIf cfg.reverseProxy.enable {
|
||||
"${fqdn}" = mkVirtualHost {
|
||||
port = cfg.config.PORT;
|
||||
ssl = cfg.reverseProxy.forceSSL;
|
||||
};
|
||||
};
|
||||
|
||||
sops = {
|
||||
secrets."miniflux/admin-password" = { };
|
||||
templates."miniflux/admin-credentials" = {
|
||||
content = ''
|
||||
ADMIN_USERNAME=${cfg.config.ADMIN_USERNAME}
|
||||
ADMIN_PASSWORD=${config.sops.placeholder."miniflux/admin-password"}
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
80
modules/nixos/nginx/default.nix
Normal file
80
modules/nixos/nginx/default.nix
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
cfg = config.services.nginx;
|
||||
|
||||
inherit (lib)
|
||||
mkDefault
|
||||
mkIf
|
||||
mkOption
|
||||
optional
|
||||
optionals
|
||||
types
|
||||
;
|
||||
in
|
||||
{
|
||||
options.services.nginx = {
|
||||
forceSSL = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Force SSL for Nginx virtual host.";
|
||||
};
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether to open the firewall for HTTP (and HTTPS if forceSSL is enabled).";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
networking.firewall.allowedTCPPorts = optionals cfg.openFirewall (
|
||||
[
|
||||
80
|
||||
]
|
||||
++ optional cfg.forceSSL 443
|
||||
);
|
||||
|
||||
services.nginx = {
|
||||
recommendedOptimisation = mkDefault true;
|
||||
recommendedGzipSettings = mkDefault true;
|
||||
recommendedProxySettings = mkDefault true;
|
||||
recommendedTlsSettings = cfg.forceSSL;
|
||||
|
||||
commonHttpConfig = "access_log syslog:server=unix:/dev/log;";
|
||||
|
||||
resolver.addresses =
|
||||
let
|
||||
isIPv6 = addr: builtins.match ".*:.*:.*" addr != null;
|
||||
escapeIPv6 = addr: if isIPv6 addr then "[${addr}]" else addr;
|
||||
cloudflare = [
|
||||
"1.1.1.1"
|
||||
"2606:4700:4700::1111"
|
||||
];
|
||||
resolvers =
|
||||
if config.networking.nameservers == [ ] then cloudflare else config.networking.nameservers;
|
||||
in
|
||||
map escapeIPv6 resolvers;
|
||||
|
||||
sslDhparam = mkIf cfg.forceSSL config.security.dhparams.params.nginx.path;
|
||||
|
||||
virtualHosts = {
|
||||
"${config.networking.domain}" = mkDefault {
|
||||
enableACME = cfg.forceSSL;
|
||||
forceSSL = cfg.forceSSL;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
security.acme = mkIf cfg.forceSSL {
|
||||
acceptTerms = true;
|
||||
defaults.email = mkDefault "postmaster@${config.networking.domain}";
|
||||
defaults.webroot = mkDefault "/var/lib/acme/acme-challenge";
|
||||
certs."${config.networking.domain}".postRun = "systemctl reload nginx.service";
|
||||
};
|
||||
|
||||
security.dhparams = mkIf cfg.forceSSL {
|
||||
enable = true;
|
||||
params.nginx = { };
|
||||
};
|
||||
};
|
||||
}
|
||||
69
modules/nixos/normalUsers/default.nix
Normal file
69
modules/nixos/normalUsers/default.nix
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.normalUsers;
|
||||
|
||||
inherit (lib)
|
||||
attrNames
|
||||
genAttrs
|
||||
mkOption
|
||||
types
|
||||
;
|
||||
in
|
||||
{
|
||||
options.normalUsers = mkOption {
|
||||
type = types.attrsOf (
|
||||
types.submodule {
|
||||
options = {
|
||||
extraGroups = mkOption {
|
||||
type = (types.listOf types.str);
|
||||
default = [ ];
|
||||
description = "Extra groups for the user";
|
||||
example = [ "wheel" ];
|
||||
};
|
||||
shell = mkOption {
|
||||
type = types.path;
|
||||
default = pkgs.zsh;
|
||||
description = "Shell for the user";
|
||||
};
|
||||
initialPassword = mkOption {
|
||||
type = types.str;
|
||||
default = "changeme";
|
||||
description = "Initial password for the user";
|
||||
};
|
||||
sshKeyFiles = mkOption {
|
||||
type = (types.listOf types.path);
|
||||
default = [ ];
|
||||
description = "SSH key files for the user";
|
||||
example = [ "/path/to/id_rsa.pub" ];
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
default = { };
|
||||
description = "Users to create. The usernames are the attribute names.";
|
||||
};
|
||||
|
||||
config = {
|
||||
# Create user groups
|
||||
users.groups = genAttrs (attrNames cfg) (userName: {
|
||||
name = userName;
|
||||
});
|
||||
|
||||
# Create users
|
||||
users.users = genAttrs (attrNames cfg) (userName: {
|
||||
name = userName;
|
||||
inherit (cfg.${userName}) extraGroups shell initialPassword;
|
||||
|
||||
isNormalUser = true;
|
||||
group = "${userName}";
|
||||
home = "/home/${userName}";
|
||||
openssh.authorizedKeys.keyFiles = cfg.${userName}.sshKeyFiles;
|
||||
});
|
||||
};
|
||||
}
|
||||
30
modules/nixos/nvidia/default.nix
Normal file
30
modules/nixos/nvidia/default.nix
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (lib) mkDefault;
|
||||
in
|
||||
{
|
||||
boot.blacklistedKernelModules = [ "nouveau" ];
|
||||
boot.extraModulePackages = [ config.hardware.nvidia.package ];
|
||||
boot.initrd.kernelModules = [ "nvidia" ];
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
nvtopPackages.nvidia
|
||||
];
|
||||
|
||||
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;
|
||||
|
||||
services.xserver.videoDrivers = [ "nvidia" ];
|
||||
}
|
||||
47
modules/nixos/ollama/default.nix
Normal file
47
modules/nixos/ollama/default.nix
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
cfg = config.services.ollama;
|
||||
domain = config.networking.domain;
|
||||
subdomain = cfg.reverseProxy.subdomain;
|
||||
fqdn = if (cfg.reverseProxy.enable && subdomain != "") then "${subdomain}.${domain}" else domain;
|
||||
|
||||
inherit (lib)
|
||||
mkDefault
|
||||
mkForce
|
||||
mkIf
|
||||
;
|
||||
|
||||
inherit (lib.utils)
|
||||
mkReverseProxyOption
|
||||
mkVirtualHost
|
||||
;
|
||||
in
|
||||
{
|
||||
options.services.ollama = {
|
||||
reverseProxy = mkReverseProxyOption "Ollama" "ollama";
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
services.ollama = {
|
||||
host = mkDefault (if cfg.reverseProxy.enable then "127.0.0.1" else "0.0.0.0");
|
||||
user = mkDefault "ollama";
|
||||
group = mkDefault "ollama";
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts = mkIf cfg.reverseProxy.enable {
|
||||
"${fqdn}" = mkVirtualHost {
|
||||
port = cfg.port;
|
||||
ssl = cfg.reverseProxy.forceSSL;
|
||||
recommendedProxySettings = mkForce false;
|
||||
extraConfig = ''
|
||||
proxy_set_header Host ${cfg.host}:${toString cfg.port};
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d ${cfg.home} 0755 ${cfg.user} ${cfg.group} -"
|
||||
];
|
||||
};
|
||||
}
|
||||
168
modules/nixos/open-webui-oci/default.nix
Normal file
168
modules/nixos/open-webui-oci/default.nix
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.services.open-webui-oci;
|
||||
|
||||
defaultEnv = {
|
||||
ANONYMIZED_TELEMETRY = "False";
|
||||
BYPASS_MODEL_ACCESS_CONTROL = "True";
|
||||
DEFAULT_LOCALE = "en";
|
||||
DEFAULT_USER_ROLE = "user";
|
||||
DO_NOT_TRACK = "True";
|
||||
ENABLE_DIRECT_CONNECTIONS = "True";
|
||||
ENABLE_IMAGE_GENERATION = "True";
|
||||
ENABLE_PERSISTENT_CONFIG = "False";
|
||||
ENABLE_SIGNUP = "False";
|
||||
ENABLE_SIGNUP_PASSWORD_CONFIRMATION = "True";
|
||||
ENABLE_USER_WEBHOOKS = "True";
|
||||
ENABLE_WEBSEARCH = "True";
|
||||
ENV = "prod";
|
||||
SCARF_NO_ANALYTICS = "True";
|
||||
USER_AGENT = "OpenWebUI";
|
||||
USER_PERMISSIONS_FEATURES_DIRECT_TOOL_SERVERS = "True";
|
||||
WEB_SEARCH_ENGINE = "DuckDuckGo";
|
||||
};
|
||||
|
||||
inherit (lib)
|
||||
concatStringsSep
|
||||
literalExpression
|
||||
mkEnableOption
|
||||
mkIf
|
||||
mkOption
|
||||
mkOverride
|
||||
optional
|
||||
types
|
||||
;
|
||||
in
|
||||
{
|
||||
options.services.open-webui-oci = {
|
||||
enable = mkEnableOption "Open WebUI container with Podman.";
|
||||
externalUrl = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = literalExpression "http://${config.networking.domain}";
|
||||
description = "Public URL to add to CORS.";
|
||||
};
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 3000;
|
||||
description = "Which port the Open-WebUI server listens to.";
|
||||
};
|
||||
environment = mkOption {
|
||||
default = { };
|
||||
type = types.attrsOf types.str;
|
||||
description = ''
|
||||
Extra environment variables for Open-WebUI.
|
||||
For more details see <https://docs.openwebui.com/getting-started/advanced-topics/env-configuration/>
|
||||
'';
|
||||
};
|
||||
environmentFile = mkOption {
|
||||
description = "Environment file to be passed to the Open WebUI container.";
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
example = "config.sops.templates.open-webui-env.path";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
virtualisation.podman = {
|
||||
enable = true;
|
||||
autoPrune.enable = true;
|
||||
dockerCompat = true;
|
||||
};
|
||||
|
||||
networking.firewall.interfaces =
|
||||
let
|
||||
matchAll = if !config.networking.nftables.enable then "podman+" else "podman*";
|
||||
in
|
||||
{
|
||||
"${matchAll}".allowedUDPPorts = [ 53 ];
|
||||
};
|
||||
|
||||
virtualisation.oci-containers.backend = "podman";
|
||||
|
||||
virtualisation.oci-containers.containers."open-webui" = {
|
||||
image = "ghcr.io/open-webui/open-webui:main";
|
||||
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"
|
||||
];
|
||||
};
|
||||
systemd.services."podman-open-webui" = {
|
||||
serviceConfig = {
|
||||
Restart = mkOverride 90 "always";
|
||||
};
|
||||
after = [
|
||||
"podman-network-open-webui_default.service"
|
||||
"podman-volume-open-webui_open-webui.service"
|
||||
];
|
||||
requires = [
|
||||
"podman-network-open-webui_default.service"
|
||||
"podman-volume-open-webui_open-webui.service"
|
||||
];
|
||||
partOf = [
|
||||
"podman-compose-open-webui-root.target"
|
||||
];
|
||||
wantedBy = [
|
||||
"podman-compose-open-webui-root.target"
|
||||
];
|
||||
};
|
||||
|
||||
systemd.services."podman-network-open-webui_default" = {
|
||||
path = [ pkgs.podman ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
ExecStop = "podman network rm -f open-webui_default";
|
||||
};
|
||||
script = ''
|
||||
podman network inspect open-webui_default || podman network create open-webui_default
|
||||
'';
|
||||
partOf = [ "podman-compose-open-webui-root.target" ];
|
||||
wantedBy = [ "podman-compose-open-webui-root.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" ];
|
||||
};
|
||||
};
|
||||
}
|
||||
16
modules/nixos/openssh/default.nix
Normal file
16
modules/nixos/openssh/default.nix
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{ lib, ... }:
|
||||
|
||||
let
|
||||
inherit (lib) mkDefault;
|
||||
in
|
||||
{
|
||||
services.openssh = {
|
||||
enable = mkDefault true;
|
||||
ports = mkDefault [ 2299 ];
|
||||
openFirewall = mkDefault true;
|
||||
settings = {
|
||||
PermitRootLogin = mkDefault "no";
|
||||
PasswordAuthentication = mkDefault false;
|
||||
};
|
||||
};
|
||||
}
|
||||
83
modules/nixos/print-server/default.nix
Normal file
83
modules/nixos/print-server/default.nix
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.services.print-server;
|
||||
domain = config.networking.domain;
|
||||
subdomain = cfg.reverseProxy.subdomain;
|
||||
fqdn = if (cfg.reverseProxy.enable && subdomain != "") then "${subdomain}.${domain}" else domain;
|
||||
port = 631;
|
||||
|
||||
inherit (lib)
|
||||
mkEnableOption
|
||||
mkIf
|
||||
mkOption
|
||||
types
|
||||
;
|
||||
|
||||
inherit (lib.utils)
|
||||
mkReverseProxyOption
|
||||
mkVirtualHost
|
||||
;
|
||||
in
|
||||
{
|
||||
options.services.print-server = {
|
||||
enable = mkEnableOption "print server";
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Open firewall for printing and avahi service.";
|
||||
};
|
||||
reverseProxy = mkReverseProxyOption "print-server" "print";
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
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
|
||||
services.avahi = {
|
||||
enable = true;
|
||||
nssmdns4 = true;
|
||||
openFirewall = cfg.openFirewall;
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts = mkIf cfg.reverseProxy.enable {
|
||||
${fqdn} = mkVirtualHost {
|
||||
inherit port;
|
||||
ssl = cfg.reverseProxy.forceSSL;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
116
modules/nixos/radicale/default.nix
Normal file
116
modules/nixos/radicale/default.nix
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.services.radicale;
|
||||
domain = config.networking.domain;
|
||||
subdomain = cfg.reverseProxy.subdomain;
|
||||
fqdn = if (cfg.reverseProxy.enable && subdomain != "") then "${subdomain}.${domain}" else domain;
|
||||
port = 5232;
|
||||
|
||||
inherit (lib)
|
||||
concatLines
|
||||
mkDefault
|
||||
mkIf
|
||||
mkOption
|
||||
types
|
||||
;
|
||||
|
||||
inherit (lib.utils)
|
||||
mkReverseProxyOption
|
||||
;
|
||||
in
|
||||
{
|
||||
options.services.radicale = {
|
||||
users = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
example = [
|
||||
"alice"
|
||||
"bob"
|
||||
];
|
||||
description = "List of users for Radicale. Each user must have a corresponding entry in the SOPS file under 'radicale/<user>'";
|
||||
};
|
||||
reverseProxy = mkReverseProxyOption "Radicale" "dav";
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
services.radicale = {
|
||||
settings = {
|
||||
server = {
|
||||
hosts = [
|
||||
"${if cfg.reverseProxy.enable then "127.0.0.1" else "0.0.0.0"}:${builtins.toString port}"
|
||||
];
|
||||
max_connections = mkDefault 20;
|
||||
max_content_length = mkDefault 500000000;
|
||||
timeout = mkDefault 30;
|
||||
};
|
||||
auth = {
|
||||
type = "htpasswd";
|
||||
delay = mkDefault 1;
|
||||
htpasswd_filename = config.sops.templates."radicale/users".path;
|
||||
htpasswd_encryption = mkDefault "sha512";
|
||||
};
|
||||
storage = {
|
||||
filesystem_folder = mkDefault "/var/lib/radicale/collections";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts = mkIf cfg.reverseProxy.enable {
|
||||
"${fqdn}" = {
|
||||
forceSSL = cfg.reverseProxy.forceSSL;
|
||||
enableACME = cfg.reverseProxy.forceSSL;
|
||||
locations = {
|
||||
"/" = {
|
||||
proxyPass = "http://localhost:${builtins.toString port}/";
|
||||
extraConfig = ''
|
||||
proxy_set_header X-Script-Name /;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_pass_header Authorization;
|
||||
'';
|
||||
};
|
||||
"/.well-known/caldav" = {
|
||||
return = "301 $scheme://$host/";
|
||||
};
|
||||
"/.well-known/carddav" = {
|
||||
return = "301 $scheme://$host/";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
environment.systemPackages = [
|
||||
pkgs.openssl
|
||||
];
|
||||
|
||||
sops =
|
||||
let
|
||||
owner = "radicale";
|
||||
group = "radicale";
|
||||
mode = "0440";
|
||||
|
||||
mkSecrets =
|
||||
users:
|
||||
builtins.listToAttrs (
|
||||
map (user: {
|
||||
name = "radicale/${user}";
|
||||
value = { inherit owner group mode; };
|
||||
}) users
|
||||
);
|
||||
|
||||
mkTemplate = users: {
|
||||
inherit owner group mode;
|
||||
content = concatLines (map (user: "${user}:${config.sops.placeholder."radicale/${user}"}") users);
|
||||
};
|
||||
in
|
||||
{
|
||||
secrets = mkSecrets cfg.users;
|
||||
templates."radicale/users" = mkTemplate cfg.users;
|
||||
};
|
||||
};
|
||||
}
|
||||
40
modules/nixos/rss-bridge/default.nix
Normal file
40
modules/nixos/rss-bridge/default.nix
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
cfg = config.services.rss-bridge;
|
||||
domain = config.networking.domain;
|
||||
subdomain = cfg.reverseProxy.subdomain;
|
||||
fqdn = if (cfg.reverseProxy.enable && subdomain != "") then "${subdomain}.${domain}" else domain;
|
||||
|
||||
inherit (lib)
|
||||
mkIf
|
||||
;
|
||||
|
||||
inherit (lib.utils)
|
||||
mkReverseProxyOption
|
||||
mkVirtualHost
|
||||
;
|
||||
in
|
||||
{
|
||||
options.services.rss-bridge = {
|
||||
reverseProxy = mkReverseProxyOption "RSS-Bridge" "rss-bridge";
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
services.rss-bridge = {
|
||||
webserver = if cfg.reverseProxy.enable then "nginx" else null;
|
||||
virtualHost = if cfg.reverseProxy.enable then fqdn else null;
|
||||
config = {
|
||||
system.enabled_bridges = [ "*" ];
|
||||
};
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [ "d ${cfg.dataDir} 0755 ${cfg.user} ${cfg.group} -" ];
|
||||
|
||||
services.nginx.virtualHosts = mkIf cfg.reverseProxy.enable {
|
||||
"${cfg.virtualHost}" = mkVirtualHost {
|
||||
ssl = cfg.reverseProxy.forceSSL;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
21
modules/nixos/sops/default.nix
Normal file
21
modules/nixos/sops/default.nix
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
inputs,
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
secrets = "${toString inputs.self}/hosts/${config.networking.hostName}/secrets/secrets.yaml";
|
||||
in
|
||||
{
|
||||
imports = [ inputs.sops-nix.nixosModules.sops ];
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
age
|
||||
sops
|
||||
];
|
||||
|
||||
sops.defaultSopsFile = lib.mkIf (builtins.pathExists secrets) (lib.mkDefault secrets);
|
||||
}
|
||||
50
modules/nixos/tailscale/default.nix
Normal file
50
modules/nixos/tailscale/default.nix
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
cfg = config.services.tailscale;
|
||||
|
||||
inherit (lib)
|
||||
mkIf
|
||||
mkOption
|
||||
optional
|
||||
types
|
||||
;
|
||||
in
|
||||
{
|
||||
options.services.tailscale = {
|
||||
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 = 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" = { };
|
||||
};
|
||||
}
|
||||
92
modules/nixos/virtualisation/default.nix
Normal file
92
modules/nixos/virtualisation/default.nix
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.virtualisation;
|
||||
|
||||
boolToZeroOne = x: if x then "1" else "0";
|
||||
|
||||
aclString = strings.concatMapStringsSep ''
|
||||
,
|
||||
'' strings.escapeNixString cfg.libvirtd.deviceACL;
|
||||
|
||||
inherit (lib)
|
||||
mkDefault
|
||||
mkOption
|
||||
optionalString
|
||||
optionals
|
||||
strings
|
||||
types
|
||||
;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
./hugepages.nix
|
||||
./kvmfr.nix
|
||||
./quickemu.nix
|
||||
./vfio.nix
|
||||
];
|
||||
|
||||
options.virtualisation = {
|
||||
libvirtd = {
|
||||
deviceACL = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
example = [
|
||||
"/dev/kvm"
|
||||
"/dev/net/tun"
|
||||
"/dev/vfio/vfio"
|
||||
];
|
||||
description = "List of device paths that QEMU processes are allowed to access.";
|
||||
};
|
||||
|
||||
clearEmulationCapabilities = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Whether to remove privileged Linux capabilities from QEMU processes after they start.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
virtualisation = {
|
||||
libvirtd = {
|
||||
enable = mkDefault true;
|
||||
onBoot = mkDefault "ignore";
|
||||
onShutdown = mkDefault "shutdown";
|
||||
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;
|
||||
};
|
||||
|
||||
users.users."qemu-libvirtd" = {
|
||||
extraGroups = optionals (!cfg.libvirtd.qemu.runAsRoot) [
|
||||
"kvm"
|
||||
"input"
|
||||
];
|
||||
isSystemUser = true;
|
||||
};
|
||||
|
||||
programs.virt-manager.enable = mkDefault true;
|
||||
|
||||
environment.systemPackages = [
|
||||
(pkgs.writeShellScriptBin "iommu-groups" (builtins.readFile ./iommu-groups.sh))
|
||||
pkgs.dnsmasq
|
||||
pkgs.qemu_full
|
||||
pkgs.virtio-win
|
||||
];
|
||||
};
|
||||
}
|
||||
45
modules/nixos/virtualisation/hugepages.nix
Normal file
45
modules/nixos/virtualisation/hugepages.nix
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.virtualisation.hugepages;
|
||||
|
||||
inherit (lib)
|
||||
mkEnableOption
|
||||
mkOption
|
||||
optionals
|
||||
types
|
||||
;
|
||||
in
|
||||
{
|
||||
options.virtualisation.hugepages = {
|
||||
enable = mkEnableOption "huge pages.";
|
||||
|
||||
defaultPageSize = mkOption {
|
||||
type = types.strMatching "[0-9]*[kKmMgG]";
|
||||
default = "1M";
|
||||
description = "Default size of huge pages. You can use suffixes K, M, and G to specify KB, MB, and GB.";
|
||||
};
|
||||
|
||||
pageSize = mkOption {
|
||||
type = types.strMatching "[0-9]*[kKmMgG]";
|
||||
default = "1M";
|
||||
description = "Size of huge pages that are allocated at boot. You can use suffixes K, M, and G to specify KB, MB, and GB.";
|
||||
};
|
||||
|
||||
numPages = mkOption {
|
||||
type = types.ints.positive;
|
||||
default = 1;
|
||||
description = "Number of huge pages to allocate at boot.";
|
||||
};
|
||||
};
|
||||
|
||||
config.boot.kernelParams = optionals cfg.enable [
|
||||
"default_hugepagesz=${cfg.defaultPageSize}"
|
||||
"hugepagesz=${cfg.pageSize}"
|
||||
"hugepages=${toString cfg.numPages}"
|
||||
];
|
||||
}
|
||||
6
modules/nixos/virtualisation/iommu-groups.sh
Normal file
6
modules/nixos/virtualisation/iommu-groups.sh
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
shopt -s nullglob
|
||||
for d in /sys/kernel/iommu_groups/*/devices/*; do
|
||||
n=${d#*/iommu_groups/*}; n=${n%%/*}
|
||||
printf 'IOMMU Group %s ' "$n"
|
||||
lspci -nns "${d##*/}"
|
||||
done;
|
||||
157
modules/nixos/virtualisation/kvmfr.nix
Normal file
157
modules/nixos/virtualisation/kvmfr.nix
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.virtualisation.kvmfr;
|
||||
|
||||
sizeFromResolution =
|
||||
resolution:
|
||||
let
|
||||
ceilToPowerOf2 =
|
||||
let
|
||||
findNextPower = target: power: if power >= target then power else findNextPower target (power * 2);
|
||||
in
|
||||
n: if n <= 1 then 1 else findNextPower n 1;
|
||||
pixelSize = if resolution.pixelFormat == "rgb24" then 3 else 4;
|
||||
bytes = resolution.width * resolution.height * pixelSize * 2;
|
||||
in
|
||||
ceilToPowerOf2 (bytes / 1024 / 1024 + 10);
|
||||
|
||||
deviceSizes = map (device: device.size) cfg.devices;
|
||||
|
||||
devices = imap (index: _deviceConfig: "/dev/kvmfr${toString index}") cfg.devices;
|
||||
|
||||
udevPackage = pkgs.writeTextDir "/lib/udev/rules.d/99-kvmfr.rules" (
|
||||
concatStringsSep "\n" (
|
||||
imap0 (index: deviceConfig: ''
|
||||
SUBSYSTEM=="kvmfr", KERNEL=="kvmfr${toString index}", OWNER="${deviceConfig.permissions.user}", GROUP="${deviceConfig.permissions.group}", MODE="${deviceConfig.permissions.mode}", TAG+="systemd"
|
||||
'') cfg.devices
|
||||
)
|
||||
);
|
||||
|
||||
apparmorAbstraction = concatStringsSep "\n" (map (device: "${device} rw") devices);
|
||||
|
||||
permissionsType = types.submodule {
|
||||
options = {
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "root";
|
||||
description = "Owner of the shared memory device.";
|
||||
};
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "root";
|
||||
description = "Group of the shared memory device.";
|
||||
};
|
||||
mode = mkOption {
|
||||
type = types.str;
|
||||
default = "0600";
|
||||
description = "Mode of the shared memory device.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
resolutionType = types.submodule {
|
||||
options = {
|
||||
width = mkOption {
|
||||
type = types.number;
|
||||
description = "Maximum horizontal video size that should be supported by this device.";
|
||||
};
|
||||
|
||||
height = mkOption {
|
||||
type = types.number;
|
||||
description = "Maximum vertical video size that should be supported by this device.";
|
||||
};
|
||||
|
||||
pixelFormat = mkOption {
|
||||
type = types.enum [
|
||||
"rgba32"
|
||||
"rgb24"
|
||||
];
|
||||
description = "Pixel format to use.";
|
||||
default = "rgba32";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
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.";
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
size = mkIf (config.resolution != null) (sizeFromResolution config.resolution);
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
inherit (lib)
|
||||
concatStringsSep
|
||||
imap
|
||||
imap0
|
||||
mkIf
|
||||
mkOption
|
||||
optionals
|
||||
types
|
||||
;
|
||||
in
|
||||
{
|
||||
options.virtualisation.kvmfr = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether to enable the kvmfr kernel module.";
|
||||
};
|
||||
|
||||
devices = mkOption {
|
||||
type = types.listOf deviceType;
|
||||
default = [ ];
|
||||
description = "List of devices to create.";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
boot.extraModulePackages = with config.boot.kernelPackages; [ kvmfr ];
|
||||
services.udev.packages = optionals (cfg.devices != [ ]) [ udevPackage ];
|
||||
|
||||
environment.systemPackages = with pkgs; [ looking-glass-client ];
|
||||
environment.etc = {
|
||||
"modules-load.d/kvmfr.conf".text = ''
|
||||
kvmfr
|
||||
'';
|
||||
|
||||
"modprobe.d/kvmfr.conf".text = ''
|
||||
options kvmfr static_size_mb=${concatStringsSep "," (map (size: toString size) deviceSizes)}
|
||||
'';
|
||||
|
||||
"apparmor.d/local/abstractions/libvirt-qemu" = mkIf config.security.apparmor.enable {
|
||||
text = apparmorAbstraction;
|
||||
};
|
||||
};
|
||||
|
||||
virtualisation.libvirtd.deviceACL = devices;
|
||||
};
|
||||
}
|
||||
28
modules/nixos/virtualisation/quickemu.nix
Normal file
28
modules/nixos/virtualisation/quickemu.nix
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.virtualisation.quickemu;
|
||||
|
||||
inherit (lib) mkEnableOption mkIf;
|
||||
in
|
||||
{
|
||||
options.virtualisation.quickemu = {
|
||||
enable = mkEnableOption "quickemu";
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
environment.systemPackages = with pkgs; [
|
||||
quickemu
|
||||
];
|
||||
|
||||
boot.extraModprobeConfig = ''
|
||||
options kvm_amd nested=1
|
||||
options kvm ignore_msrs=1 report_ignored_msrs=0
|
||||
'';
|
||||
};
|
||||
}
|
||||
103
modules/nixos/virtualisation/vfio.nix
Normal file
103
modules/nixos/virtualisation/vfio.nix
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.virtualisation.vfio;
|
||||
|
||||
inherit (lib)
|
||||
mkEnableOption
|
||||
mkIf
|
||||
mkOption
|
||||
optional
|
||||
optionals
|
||||
types
|
||||
versionOlder
|
||||
;
|
||||
in
|
||||
{
|
||||
options.virtualisation.vfio = {
|
||||
enable = mkEnableOption "VFIO Configuration";
|
||||
|
||||
IOMMUType = mkOption {
|
||||
type = types.enum [
|
||||
"intel"
|
||||
"amd"
|
||||
];
|
||||
example = "intel";
|
||||
description = "Type of the IOMMU used";
|
||||
};
|
||||
|
||||
devices = mkOption {
|
||||
type = types.listOf (types.strMatching "[0-9a-f]{4}:[0-9a-f]{4}");
|
||||
default = [ ];
|
||||
example = [
|
||||
"10de:1b80"
|
||||
"10de:10f0"
|
||||
];
|
||||
description = "PCI IDs of devices to bind to vfio-pci";
|
||||
};
|
||||
|
||||
disableEFIfb = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
example = true;
|
||||
description = "Disables the usage of the EFI framebuffer on boot.";
|
||||
};
|
||||
|
||||
blacklistNvidia = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Add Nvidia GPU modules to blacklist";
|
||||
};
|
||||
|
||||
ignoreMSRs = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
example = true;
|
||||
description = "Enables or disables kvm guest access to model-specific registers";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
services.udev.extraRules = ''
|
||||
SUBSYSTEM=="vfio", OWNER="root", GROUP="kvm"
|
||||
'';
|
||||
|
||||
boot.kernelParams =
|
||||
(
|
||||
if cfg.IOMMUType == "intel" then
|
||||
[
|
||||
"intel_iommu=on"
|
||||
"intel_iommu=igfx_off"
|
||||
]
|
||||
else
|
||||
[ "amd_iommu=on" ]
|
||||
)
|
||||
++ optional (cfg.devices != [ ]) ("vfio-pci.ids=" + builtins.concatStringsSep "," cfg.devices)
|
||||
++ (optional cfg.disableEFIfb "video=efifb:off")
|
||||
++ (
|
||||
optionals cfg.ignoreMSRs [
|
||||
"kvm.ignore_msrs=1"
|
||||
"kvm.report_ignored_msrs=0"
|
||||
]
|
||||
++ optional cfg.blacklistNvidia "modprobe.blacklist=nouveau,nvidia,nvidia_drm,nvidia"
|
||||
);
|
||||
|
||||
boot.initrd.kernelModules = [
|
||||
"vfio_pci"
|
||||
"vfio_iommu_type1"
|
||||
"vfio"
|
||||
]
|
||||
++ optionals (versionOlder pkgs.linux.version "6.2") [ "vfio_virqfd" ];
|
||||
|
||||
# boot.blacklistedKernelModules = optionals cfg.blacklistNvidia [
|
||||
# "nouveau"
|
||||
# "nvidia"
|
||||
# "nvidia_drm"
|
||||
# ];
|
||||
};
|
||||
}
|
||||
66
modules/nixos/webPage/default.nix
Normal file
66
modules/nixos/webPage/default.nix
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
cfg = config.services.webPage;
|
||||
domain = config.networking.domain;
|
||||
fqdn = if (cfg.subdomain != "") then "${cfg.subdomain}.${domain}" else domain;
|
||||
nginxUser = config.services.nginx.user;
|
||||
|
||||
inherit (lib)
|
||||
mkEnableOption
|
||||
mkIf
|
||||
mkOption
|
||||
types
|
||||
;
|
||||
in
|
||||
{
|
||||
options.services.webPage = {
|
||||
enable = mkEnableOption "static web page hosting";
|
||||
subdomain = mkOption {
|
||||
type = types.str;
|
||||
default = "www";
|
||||
description = "The subdomain to serve the web page on. Leave empty for root domain.";
|
||||
};
|
||||
forceSSL = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Force SSL for Nginx virtual host.";
|
||||
};
|
||||
webRoot = mkOption {
|
||||
type = types.str;
|
||||
description = "The root directory of the web page.";
|
||||
example = "/var/www";
|
||||
};
|
||||
test = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "If true, a sample index.html will be placed in the web root for testing purposes.";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
services.nginx.virtualHosts."${fqdn}" = {
|
||||
enableACME = cfg.forceSSL;
|
||||
forceSSL = cfg.forceSSL;
|
||||
root = cfg.webRoot;
|
||||
locations."/".index = "index.html";
|
||||
sslCertificate = mkIf cfg.forceSSL "${config.security.acme.certs."${fqdn}".directory}/cert.pem";
|
||||
sslCertificateKey = mkIf cfg.forceSSL "${config.security.acme.certs."${fqdn}".directory}/key.pem";
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [ "d ${cfg.webRoot} 0755 ${nginxUser} ${nginxUser} -" ];
|
||||
|
||||
system.activationScripts.webPagePermissions = ''
|
||||
chown -R ${nginxUser}:${nginxUser} ${cfg.webRoot}
|
||||
chmod -R 0755 ${cfg.webRoot}
|
||||
'';
|
||||
|
||||
# test page
|
||||
environment.etc."sample.html" = mkIf cfg.test { source = ./sample.html; };
|
||||
system.activationScripts.moveSampleHtmlToWebRoot = mkIf cfg.test ''
|
||||
mv /etc/sample.html ${cfg.webRoot}/index.html
|
||||
chown -R ${nginxUser}:${nginxUser} ${cfg.webRoot}
|
||||
chmod -R 0755 ${cfg.webRoot}
|
||||
'';
|
||||
};
|
||||
}
|
||||
12
modules/nixos/webPage/sample.html
Normal file
12
modules/nixos/webPage/sample.html
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Test Page</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Test Page</h1>
|
||||
<p>This is a sample index.html file for testing purposes.</p>
|
||||
</body>
|
||||
</html>
|
||||
170
modules/nixos/windows-oci/default.nix
Normal file
170
modules/nixos/windows-oci/default.nix
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.services.windows-oci;
|
||||
|
||||
inherit (lib)
|
||||
mkEnableOption
|
||||
mkIf
|
||||
mkOption
|
||||
mkOverride
|
||||
optional
|
||||
types
|
||||
;
|
||||
in
|
||||
{
|
||||
options.services.windows-oci = {
|
||||
enable = mkEnableOption "Windows in an OCI container using Podman";
|
||||
volume = mkOption {
|
||||
type = types.str;
|
||||
default = "/opt/windows";
|
||||
description = "Path to the volume for Windows data.";
|
||||
};
|
||||
sharedVolume = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = "Path to a shared volume to mount inside the Windows container. You have to create this directory manually.";
|
||||
};
|
||||
settings = {
|
||||
version = mkOption {
|
||||
type = types.str;
|
||||
default = "11";
|
||||
example = "2025";
|
||||
description = "Windows version to use.";
|
||||
};
|
||||
ramSize = mkOption {
|
||||
type = types.str;
|
||||
default = "8G";
|
||||
description = "Amount of RAM to allocate to the Windows container.";
|
||||
};
|
||||
cpuCores = mkOption {
|
||||
type = types.str;
|
||||
default = "4";
|
||||
description = "Number of CPU cores to allocate to the Windows container.";
|
||||
};
|
||||
diskSize = mkOption {
|
||||
type = types.str;
|
||||
default = "64G";
|
||||
description = "Size of the virtual disk for the Windows container.";
|
||||
};
|
||||
username = mkOption {
|
||||
type = types.str;
|
||||
default = "admin";
|
||||
description = "Username for the Windows installation.";
|
||||
};
|
||||
password = mkOption {
|
||||
type = types.str;
|
||||
default = "admin";
|
||||
description = "Password for the Windows installation.";
|
||||
};
|
||||
language = mkOption {
|
||||
type = types.str;
|
||||
default = "English";
|
||||
description = "Language for the Windows installation.";
|
||||
};
|
||||
region = mkOption {
|
||||
type = types.str;
|
||||
default = "en-DE";
|
||||
description = "Region for the Windows installation.";
|
||||
};
|
||||
keyboard = mkOption {
|
||||
type = types.str;
|
||||
default = "de-DE";
|
||||
description = "Keyboard layout for the Windows installation.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.tmpfiles.rules = [ "d ${cfg.volume} 0755 root podman -" ];
|
||||
|
||||
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" ];
|
||||
};
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue