copy nb configuration and modules

This commit is contained in:
2023-07-12 16:13:10 +02:00
parent 1af70a3095
commit 127eab91d5
114 changed files with 9070 additions and 0 deletions

View File

@@ -0,0 +1,7 @@
{ config, ... }:
{
system.autoUpgrade.enable = true;
system.autoUpgrade.allowReboot = false;
system.autoUpgrade.channel = "https://channels.nixos.org/nixos-23.05";
}

View File

@@ -0,0 +1,36 @@
{
lib,
pkgs,
...
}: let
create_users = host: {
users.users."${host.username}" = {
createHome = false;
home = "/home/chroot/" + host.username;
isNormalUser = false;
isSystemUser = true;
group = "sftp_users";
openssh.authorizedKeys.keys = [host.key];
shell = null;
};
};
users = [
{
username = "notebook";
key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDN/2SAFm50kraB1fepAizox/QRXxB7WbqVbH+5OPalDT47VIJGNKOKhixQoqhABHxEoLxdf/C83wxlCVlPV9poLfDgVkA3Lyt5r3tSFQ6QjjOJAgchWamMsxxyGBedhKvhiEzcr/Lxytnoz3kjDG8fqQJwEpdqMmJoMUfyL2Rqp16u+FQ7d5aJtwO8EUqovhMaNO7rggjPpV/uMOg+tBxxmscliN7DLuP4EMTA/FwXVzcFNbOx3K9BdpMRAaSJt4SWcJO2cS2KHA5n/H+PQI7nz5KN3Yr/upJN5fROhi/SHvK39QOx12Pv7FCuWlc+oR68vLaoCKYhnkl3DnCfc7A7";
}
];
in {
imports = builtins.map create_users users;
users.groups = {sftp_users = {};};
services.openssh.extraConfig = ''
Match Group sftp_users
X11Forwarding no
AllowTcpForwarding no
ChrootDirectory %h
ForceCommand internal-sftp
'';
}

116
utils/modules/bitwarden.nix Normal file
View File

@@ -0,0 +1,116 @@
{
pkgs,
config,
...
}: let
ldapConfig = {
vaultwarden_url = "https://bitwarden.cloonar.com";
vaultwarden_admin_token = "@ADMIN_TOKEN@";
ldap_host = "localhost";
ldap_bind_dn = "cn=vmail,dc=cloonar,dc=com";
ldap_bind_password = "@LDAP_PASSWORD@";
ldap_search_base_dn = "dc=cloonar,dc=com";
ldap_search_filter = "(&(objectClass=inetOrgPerson))";
ldap_sync_interval_seconds = 3600;
};
ldapConfigFile =
pkgs.runCommand "config.toml"
{
buildInputs = [pkgs.remarshal];
preferLocalBuild = true;
} ''
remarshal -if json -of toml \
< ${pkgs.writeText "config.json" (builtins.toJSON ldapConfig)} \
> $out
'';
in {
packageOverrides = pkgs: {
nur = import (builtins.fetchTarball "https://github.com/nix-community/NUR/archive/master.tar.gz") {
inherit pkgs;
};
};
environment.systemPackages = with pkgs; [
nur.repos.mic92.vaultwarden_ldap
];
services.vaultwarden = {
enable = true;
dbBackend = "mysql";
config = {
domain = "https://bitwarden.cloonar.com";
signupsAllowed = false;
rocketPort = 3011;
databaseUrl = "mysql://bitwarden:<${config.sops.secrets.bitwarden-db-password.path}@localhost/bitwarden";
enableDbWal = "false";
websocketEnabled = true;
smtpHost = "smtp.cloonar.com";
smtpFrom = "bitwarden@cloonar.com";
smtpUsername = "bitwarden@cloonar.com";
};
};
systemd.services.vaultwarden.serviceConfig = {
EnvironmentFile = [config.sops.secrets.bitwarden-smtp-password.path];
};
systemd.services.vaultwarden_ldap = {
wantedBy = ["multi-user.target"];
preStart = ''
sed \
-e "s=@LDAP_PASSWORD@=$(<${config.sops.secrets.bitwarden-ldap-password.path})=" \
-e "s=@ADMIN_TOKEN@=$(<${config.sops.secrets.bitwarden-admin-token.path})=" \
${ldapConfigFile} \
> /run/vaultwarden_ldap/config.toml
'';
serviceConfig = {
Restart = "on-failure";
RestartSec = "2s";
ExecStart = "${config.nur.repos.mic92.vaultwarden_ldap}/bin/vaultwarden_ldap";
Environment = "CONFIG_PATH=/run/vaultwarden_ldap/config.toml";
RuntimeDirectory = ["vaultwarden_ldap"];
User = "vaultwarden_ldap";
};
};
services.nginx = {
virtualHosts."bitwarden.cloonar.com" = {
forceSSL = true;
enableACME = true;
acmeRoot = null;
extraConfig = ''
client_max_body_size 128M;
'';
locations."/" = {
proxyPass = "http://localhost:3011";
proxyWebsockets = true;
};
locations."/notifications/hub" = {
proxyPass = "http://localhost:3012";
proxyWebsockets = true;
};
locations."/notifications/hub/negotiate" = {
proxyPass = "http://localhost:3011";
proxyWebsockets = true;
};
};
};
sops.secrets = {
bitwarden-admin-token.owner = "vaultwarden_ldap";
bitwarden-ldap-password.owner = "vaultwarden_ldap";
bitwarden-db-password.owner = "vaultwarden";
bitwarden-smtp-password.owner = "vaultwarden";
};
users.users.vaultwarden_ldap = {
isSystemUser = true;
group = "vaultwarden_ldap";
};
users.groups.vaultwarden_ldap = {};
}

View File

@@ -0,0 +1,126 @@
{
pkgs,
config,
...
}: let
ldapConfig = {
vaultwarden_url = "https://bitwarden.cloonar.com";
vaultwarden_admin_token = "@ADMIN_TOKEN@";
ldap_host = "ldap.cloonar.com";
ldap_ssl = true;
ldap_bind_dn = "cn=bitwarden,ou=system,ou=users,dc=cloonar,dc=com";
ldap_bind_password = "@LDAP_PASSWORD@";
ldap_search_base_dn = "ou=users,dc=cloonar,dc=com";
ldap_search_filter = "(&(objectClass=cloonarUser))";
ldap_sync_interval_seconds = 3600;
};
ldapConfigFile =
pkgs.runCommand "config.toml"
{
buildInputs = [pkgs.remarshal];
preferLocalBuild = true;
} ''
remarshal -if json -of toml \
< ${pkgs.writeText "config.json" (builtins.toJSON ldapConfig)} \
> $out
'';
in {
imports = [
../nur.nix
];
environment.systemPackages = with pkgs; [
nur.repos.mic92.vaultwarden_ldap
];
services.vaultwarden = {
enable = true;
dbBackend = "mysql";
config = {
domain = "https://bitwarden.cloonar.com";
signupsAllowed = false;
rocketPort = 3011;
enableDbWal = "false";
websocketEnabled = true;
smtpHost = "mail.cloonar.com";
smtpFrom = "bitwarden@cloonar.com";
smtpUsername = "bitwarden@cloonar.com";
};
};
systemd.services.vaultwarden.serviceConfig = {
EnvironmentFile = [config.sops.secrets.bitwarden-smtp-password.path];
};
systemd.services.vaultwarden_ldap = {
wantedBy = ["multi-user.target"];
preStart = ''
sed \
-e "s=@LDAP_PASSWORD@=$(<${config.sops.secrets.bitwarden-ldap-password.path})=" \
-e "s=@ADMIN_TOKEN@=$(<${config.sops.secrets.bitwarden-admin-token.path})=" \
${ldapConfigFile} \
> /run/vaultwarden_ldap/config.toml
'';
serviceConfig = {
Restart = "on-failure";
RestartSec = "2s";
ExecStart = "${pkgs.nur.repos.mic92.vaultwarden_ldap}/bin/vaultwarden_ldap";
Environment = "CONFIG_PATH=/run/vaultwarden_ldap/config.toml";
RuntimeDirectory = ["vaultwarden_ldap"];
User = "vaultwarden_ldap";
};
};
services.nginx.virtualHosts."bitwarden.cloonar.com" = {
forceSSL = true;
enableACME = true;
acmeRoot = null;
extraConfig = ''
client_max_body_size 128M;
'';
locations."/" = {
proxyPass = "http://localhost:3011";
proxyWebsockets = true;
};
locations."/notifications/hub" = {
proxyPass = "http://localhost:3012";
proxyWebsockets = true;
};
locations."/notifications/hub/negotiate" = {
proxyPass = "http://localhost:3011";
proxyWebsockets = true;
};
};
sops.secrets = {
bitwarden-admin-token = {
owner = "vaultwarden_ldap";
sopsFile = ./secrets.yaml;
};
bitwarden-ldap-password = {
owner = "vaultwarden_ldap";
sopsFile = ./secrets.yaml;
};
bitwarden-db-password = {
owner = "vaultwarden";
sopsFile = ./secrets.yaml;
};
bitwarden-smtp-password = {
owner = "vaultwarden";
sopsFile = ./secrets.yaml;
};
};
users.users.vaultwarden_ldap = {
isSystemUser = true;
group = "vaultwarden_ldap";
};
users.groups.vaultwarden_ldap = {};
services.mysqlBackup.databases = [ "bitwarden" ];
}

View File

@@ -0,0 +1,33 @@
bitwarden-admin-token: ENC[AES256_GCM,data:nCj7kwQHTwezG3hh5J+c2MmUXwlGpdNjeh4A4SK/wgdBroAAghMSTuT6B7sjPgX5PmyBpzspdI3XqVUoBHzL6g==,iv:11C/ScaTqI1VlBSd71TA2cZNAu/wSbOs6rnDTlKlPsI=,tag:8eD0VkJn/KZ49yMe4D/MrA==,type:str]
bitwarden-db-password: ENC[AES256_GCM,data:4l3ntOHX4pdiUzfSqOwzObgMRp9eS5fjze6rJu1h3kKr/g/lsESLWiIHUoguixaNmoPU2zy42jEDvhXII6R+1g==,iv:mEMGGGyWerJaAvo7ymNfkR1YgTG1ieB3n40BB6L+UM4=,tag:iRd88BjFMMht9Ku9K34SXQ==,type:str]
bitwarden-ldap-password: ENC[AES256_GCM,data:g6tp0NzXk3ZJTGKHSzFxVZs4DhauzPS6SGW99WFX/CO0Wprgp9lh/evI6T56g2YhIv/3jqNSmi+p1FwdOzValw==,iv:mHMlhJx2aKLLkrPy+Z+/6plS/uMiK+xhYk/PF5m7+wQ=,tag:BgRNstiVnN95/pSX0DYfSw==,type:str]
bitwarden-smtp-password: ENC[AES256_GCM,data:4ruP8yMeTG5A19Oyvv2MBTj2LwecwwYc8BBU1xDT2i757orCNrQHJd0VLtzynluS9ge4vAU7G8islKwR/IIDGsEq74//CxJIyXyH9XLBfc5Jb2Rs1uz/Nz2uCWOCqm1AZ2/8uxXOPPNVhKcs3wxOLbLnA3Yzh+VFKsKIO753FkKllpFbeZanhfD2/N4fAGU4C5F+0HcrLBLBGC3X/CfQyPUSio1uwWPxRJR94DlRdPq+ir4YXHW48Mw/33lJZ+HqApk1Nf+gmTff7XTib1d44ac4JR8m20D8qOQ2Y9vfqJOxD7/PdgeqRLXN3K1PaSDE7JkWoiE0dM3vJ0q+Pqf47tm/xT4qaJvqI0jLXMwqmUg=,iv:TiZrLMPx9UbUf/4zKmRWTERM8phtyTX7Q3dCFqn+Ew4=,tag:55tuxMBWu6WpT4BllKV+pA==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age16veg3fmvpfm7a89a9fc8dvvsxmsthlm70nfxqspr6t8vnf9wkcwsvdq38d
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBMUmttaXRmcXh2UDRyL1pJ
MmxMSzBqUmxlY290K1djQ3ZSYWM1OXhFbEhvCjlmNnd6T2Z2bHNZK3oxRmllNUFI
RzNvc3hqMXIwd3dwa3crckxVNmNyQXcKLS0tIHFLSXpUOFpyaUVXa2hJU0pheU5h
NlZoeVNYdTQ1c0pUQ1o4NnJxaWFTTFEK0l7vHpXj00fUFno5gjS3apPRWercgng6
SOygmyQiP/EWI/G0M6W8gjq76pQamYJWVVJVFwxqg4BxAhdMwxtVXQ==
-----END AGE ENCRYPTED FILE-----
- recipient: age1y6lvl5jkwc47p5ae9yz9j9kuwhy7rtttua5xhygrgmr7ehd49svsszyt42
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBCNjE2c21zVktrTUJDVWJ2
UWdqZHErWkpGVnY5Q0F3QUlBNkprRFJFbHpjCnJvQmZoZ0x2c0dxU0FwbExGaTIz
eGFoczZma2dBVEJjQ1ljb24zYWJEQWsKLS0tIDVmWWQyYmQ1RS9oV0p0ZmwyUzJI
NkdzbFczM0VteW5ESGFNcmNJdFRNODAK8HRoc2thwAH8pvnMwesRs8OesVGHW/Iu
viPtt+w6FNwYt4LXnFlBMdGXfrWE0fWxokM0sm5GglKuFGn1zf3+VA==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2023-01-28T21:53:06Z"
mac: ENC[AES256_GCM,data:jZq4UzkxyX/UhrmeKO7sFQpTlMB13lyi5/duXA0s2XX3W0U9g+TSZm21WiRGPjKmteJg0w2OhFsNk/y0uvD/oPE1ttLz/YRgiinuCoyufoX51AgQqS0KFxNBkTaDzoaKk3z1j8nEhAY2U0YS4fpOCNAkMsKdVZeTVOitcp/UeIE=,iv:5EzYCqUZri1VmD9wqQGxpypZe4F2h8W3D8a7mYbBBrg=,tag:iEFJBFmRJVw4YP5/V+21dQ==,type:str]
pgp: []
unencrypted_suffix: _unencrypted
version: 3.7.3

View File

@@ -0,0 +1,87 @@
{ pkgs, config, lib, ... }:
let
repo = config.borgbackup.repo;
#repo = config.borgrepo;
#repo = "u149513-sub3@u149513-sub3.your-backup.de:borg";
borgMount = pkgs.writeShellScriptBin "borg-mount" ''
export BORG_PASSCOMMAND='cat ${config.sops.secrets.borg-passphrase.path}'
borg mount --rsh "ssh -p23 -i ${config.sops.secrets.borg-ssh-key.path}" ${repo}::$1 $2
'';
borgList = pkgs.writeShellScriptBin "borg-list" ''
export BORG_PASSCOMMAND='cat ${config.sops.secrets.borg-passphrase.path}'
borg --rsh "ssh -p23 -i ${config.sops.secrets.borg-ssh-key.path}" list ${repo}
'';
borgBackup = pkgs.writeShellScriptBin "borg-backup" ''
systemctl restart borgbackup-job-default.service
'';
borgRestore = pkgs.writeShellScriptBin "borg-restore" ''
cd /
export BORG_PASSCOMMAND='cat ${config.sops.secrets.borg-passphrase.path}'
borg --rsh "ssh -p23 -i ${config.sops.secrets.borg-ssh-key.path}" list ${repo}
borg extract --list --rsh "ssh -p23 -i ${config.sops.secrets.borg-ssh-key.path}" ${repo}::$1
'';
in {
options = with lib; with types; {
borgbackup = mkOption {
description = "Options for borg module";
type = submodule {
options.repo = mkOption {
type = types.str;
description = "borg repo";
};
};
};
};
config = {
sops.secrets.borg-passphrase = {};
sops.secrets.borg-ssh-key = {};
environment.systemPackages = [
borgMount
borgList
borgBackup
borgRestore
];
services.borgbackup.jobs.default = {
paths = [
"/home"
"/var"
"/root"
];
exclude = [
"/var/lib/containerd"
# already included in database backup
"/var/lib/mysql"
"/var/lib/postgresql"
"/var/lib/docker/"
"/var/log"
"/var/cache"
"/var/tmp"
"/var/log"
];
environment.BORG_RSH = "ssh -p23 -i ${config.sops.secrets.borg-ssh-key.path}";
repo = repo;
encryption = {
mode = "repokey";
passCommand = "cat ${config.sops.secrets.borg-passphrase.path}";
};
compression = "auto,zstd";
startAt = "*-*-* 03:00:00";
prune.keep = {
within = "1d"; # Keep all archives from the last day
daily = 7;
weekly = 4;
monthly = 6;
};
};
};
}

View File

@@ -0,0 +1,38 @@
let
# NixOS 22.11 as of 2023-01-12
nixpkgs = builtins.getFlake "github:nixos/nixpkgs/54644f409ab471e87014bb305eac8c50190bcf48";
sys = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
({ config, pkgs, lib, modulesPath, ... }: {
imports = [
(modulesPath + "/installer/netboot/netboot-minimal.nix")
];
config = {
## Some useful options for setting up a new system
# services.getty.autologinUser = lib.mkForce "root";
# users.users.root.openssh.authorizedKeys.keys = [ ... ];
# console.keyMap = "de";
# hardware.video.hidpi.enable = true;
system.stateVersion = config.system.nixos.release;
};
})
];
};
run-pixiecore = let
hostPkgs = if sys.pkgs.system == builtins.currentSystem
then sys.pkgs
else nixpkgs.legacyPackages.${builtins.currentSystem};
build = sys.config.system.build;
in hostPkgs.writers.writeBash "run-pixiecore" ''
exec ${hostPkgs.pixiecore}/bin/pixiecore \
boot ${build.kernel}/bzImage ${build.netbootRamdisk}/initrd \
--cmdline "init=${build.toplevel}/init loglevel=4" \
--debug --dhcp-no-bind \
--port 64172 --status-port 64172 "$@"
'';
in
run-pixiecore

55
utils/modules/clevis.nix Normal file
View File

@@ -0,0 +1,55 @@
{ config, lib, pkgs, ... }:
with lib;
let
uuid = "";
cfg = config.services.clevis;
in
{
options.services.clevis = {
uuid = mkOption {
type = types.str;
default = "";
description = lib.mdDoc ''
UUID of device to decrypt with clevis.
'';
};
};
config = {
environment.systemPackages = with pkgs; [
clevis
];
boot.initrd.extraUtilsCommands = ''
# clevis dependencies
copy_bin_and_libs ${pkgs.curl}/bin/curl
copy_bin_and_libs ${pkgs.bash}/bin/bash
copy_bin_and_libs ${pkgs.jose}/bin/jose
# clevis scripts and binaries
for i in ${pkgs.clevis}/bin/* ${pkgs.clevis}/bin/.clevis-wrapped; do
copy_bin_and_libs "$i"
done
'';
boot.initrd.luks.devices."nixos-enc" = {
device = "/dev/disk/by-uuid/${cfg.uuid}";
preOpenCommands = with pkgs; ''
# what would be a sensible way of automating this? at the very least the versions should not be hard coded
ln -s ../.. /nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-${bash.name}
ln -s ../.. /nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-${clevis.name}
ln -s ../.. /nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-${coreutils.name}
# this runs in the background so that /crypt-ramfs/device gets set up, which implies crypt-askpass
# is ready to receive an input which it will write to /crypt-ramfs/passphrase.
# for some reason writing that file directly does not seem to work, which is why the pipe is used.
# the clevis_luks_unlock_device function is equivalent to the clevis-luks-pass command but avoid
# needing to pass the slot argument.
# using clevis-luks-unlock directly can successfully open the luks device but requires the name
# argument to be passed and will not be detected by the stage-1 luks root stuff.
bash -e -c 'while [ ! -f /crypt-ramfs/device ]; do sleep 1; done; . /bin/clevis-luks-common-functions; clevis_luks_unlock_device "$(cat /crypt-ramfs/device)" | cryptsetup-askpass' &
'';
};
};
}

View File

@@ -0,0 +1,60 @@
{ config, lib, pkgs, stdenv, ... }:
let
deconz-full = pkgs.callPackage ./pkg/default.nix { };
deconz = deconz-full.deCONZ;
in
{
environment.systemPackages = with pkgs; [
deconz
];
users.users."deconz" = {
createHome = true;
isSystemUser = true;
group = "dialout";
home = "/home/deconz";
};
systemd.services.deconz = {
enable = true;
description = "deconz";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
stopIfChanged = false;
serviceConfig = {
ExecStart = "${deconz}/bin/deCONZ -platform minimal --http-port=8080 --ws-port=8081 --http-listen=127.0.0.1 --dev=/dev/ttyACM0";
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
Restart = "always";
RestartSec = "10s";
# StartLimitInterval = "1min";
# StateDirectory = "/var/lib/deconz";
User = "deconz";
# DeviceAllow = "char-ttyUSB rwm";
# DeviceAllow = "char-usb_device rwm";
# AmbientCapabilities="CAP_NET_BIND_SERVICE CAP_KILL CAP_SYS_BOOT CAP_SYS_TIME";
};
};
services.nginx.virtualHosts."deconz.cloonar.com" = {
forceSSL = true;
enableACME = true;
acmeRoot = null;
extraConfig = ''
proxy_buffering off;
'';
locations."/".extraConfig = ''
set $p 8080;
if ($http_upgrade = "websocket") {
set $p 8081;
}
proxy_pass http://127.0.0.1:$p;
proxy_set_header Host $host;
proxy_redirect http:// https://;
proxy_http_version 1.1;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
'';
};
}

View File

@@ -0,0 +1,50 @@
{ config, pkgs, stdenv, buildFHSUserEnv, fetchurl, dpkg, qt5, sqlite, hicolor-icon-theme, libcap, libpng, libxcrypt-legacy, ... }:
#ith import <nixpkgs> {};
let
version = "2.21.02";
name = "deconz-${version}";
in
rec {
deCONZ-deb = stdenv.mkDerivation {
#builder = ./builder.sh;
inherit name;
dpkg = dpkg;
src = fetchurl {
url = "https://deconz.dresden-elektronik.de/ubuntu/stable/${name}-qt5.deb";
sha256 = "2d5ab8af471ffa82fb0fd0c8a2f0bb09e7c0bd9a03ef887abe49c616c63042f0";
};
dontConfigure = true;
dontBuild = true;
dontStrip = true;
buildInputs = [ dpkg sqlite hicolor-icon-theme libcap libpng qt5.qtbase qt5.qtserialport qt5.qtwebsockets qt5.wrapQtAppsHook libxcrypt-legacy ]; # qt5.qtserialport qt5.qtwebsockets ];
unpackPhase = "dpkg-deb -x $src .";
installPhase = ''
cp -r usr/* .
cp -r ${libxcrypt-legacy}/lib/* share/deCONZ/plugins/
cp -r share/deCONZ/plugins/* lib/
cp -r . $out
'';
};
deCONZ = buildFHSUserEnv {
name = "deCONZ";
targetPkgs = pkgs: [
deCONZ-deb
];
multiPkgs = pkgs: [
dpkg
qt5.qtbase
qt5.qtserialport
qt5.qtwebsockets
qt5.wrapQtAppsHook
sqlite
hicolor-icon-theme
libcap
libpng
];
runScript = "deCONZ";
};
}

264
utils/modules/dovecot.nix Normal file
View File

@@ -0,0 +1,264 @@
{ pkgs
, config
, ...
}:
let
domain = config.networking.domain;
# domain = "cloonar.com";
ldapConfig = pkgs.writeText "dovecot-ldap.conf" ''
hosts = ldap.cloonar.com
tls = yes
dn = "cn=vmail,ou=system,ou=users,dc=cloonar,dc=com"
dnpass = "@ldap-password@"
auth_bind = no
ldap_version = 3
base = ou=users,dc=%Dd
user_filter = (&(objectClass=mailAccount)(mail=%u))
user_attrs = \
quota=quota_rule=*:bytes=%$, \
=home=/var/vmail/%d/%n/, \
=mail=maildir:/var/vmail/%d/%n/Maildir
pass_attrs = mail=user,userPassword=password
pass_filter = (&(objectClass=mailAccount)(mail=%u))
iterate_attrs = =user=%{ldap:mail}
iterate_filter = (objectClass=mailAccount)
scope = subtree
default_pass_scheme = CRYPT
'';
doveSync = pkgs.writeShellScriptBin "dove-sync.sh" ''
#!/usr/bin/env bash
SERVER=''${1}
if [ -z "$SERVER" ]; then
echo "use as dove-sync.sh host.example.com"
exit 1
fi
doveadm user *@cloonar.com | while read user; do
doveadm -v sync -u $user $SERVER
done
doveadm user *@optiprot.eu | while read user; do
doveadm -v sync -u $user $SERVER
done
doveadm user *@superbros.tv | while read user; do
doveadm -v sync -u $user $SERVER
done
doveadm user *@ghetto.at | while read user; do
doveadm -v sync -u $user $SERVER
done
doveadm user *@szaku-consulting.at | while read user; do
doveadm -v sync -u $user $SERVER
done
'';
quotaWarning = pkgs.writeShellScriptBin "quota-warning.sh" ''
#!/usr/bin/env bash
PERCENT=''${1}
USER=''${2}
cat << EOF | /usr/lib/dovecot/deliver -d ''${USER} -o "plugin/quota=dict:User quota::noenforcing:proxy::quotadict"
From: no-reply@$(hostname -f)
Subject: Warning: Your mailbox is now ''${PERCENT}% full.
Your mailbox is now ''${PERCENT}% full, please clean up some mails for further incoming mails.
EOF
if [ ''${PERCENT} -ge 95 ]; then
DOMAIN="$(echo ''${USER} | awk -F'@' '{print $2}')"
cat << EOF | /usr/lib/dovecot/deliver -d postmaster@''${DOMAIN} -o "plugin/quota=dict:User quota::noenforcing:proxy::quotadict"
From: no-reply@$(hostname -f)
Subject: Mailbox Quota Warning: ''${PERCENT}% full, ''${USER}
Mailbox (''${USER}) is now ''${PERCENT}% full, please clean up some mails for
further incoming mails.
EOF
fi
'';
in
{
environment.systemPackages = with pkgs; [
doveSync
];
services.dovecot2 = {
enable = true;
enableImap = true;
enableLmtp = true;
enablePAM = false;
mailLocation = "maildir:/var/vmail/%d/%n/Maildir";
mailUser = "vmail";
mailGroup = "vmail";
extraConfig = ''
ssl = yes
ssl_cert = </var/lib/acme/imap.${domain}/fullchain.pem
ssl_key = </var/lib/acme/imap.${domain}/key.pem
ssl_min_protocol = TLSv1.2
ssl_cipher_list = EECDH+AESGCM:EDH+AESGCM
ssl_prefer_server_ciphers = yes
ssl_dh=<${config.security.dhparams.params.dovecot2.path}
mail_plugins = virtual fts fts_lucene quota acl
service lmtp {
user = vmail
unix_listener /var/lib/postfix/queue/private/dovecot-lmtp {
group = postfix
mode = 0600
user = postfix
}
}
service doveadm {
inet_listener {
port = 4170
ssl = yes
}
}
protocol imap {
mail_plugins = $mail_plugins imap_quota imap_acl
}
protocol lmtp {
postmaster_address=postmaster@${domain}
hostname=mail.cloonar.com
mail_plugins = $mail_plugins sieve
}
service auth {
unix_listener auth-userdb {
mode = 0640
user = vmail
group = vmail
}
# Postfix smtp-auth
unix_listener /var/lib/postfix/queue/private/auth {
mode = 0666
user = postfix
group = postfix
}
}
userdb {
args = /run/dovecot2/ldap.conf
driver = ldap
}
passdb {
args = /run/dovecot2/ldap.conf
driver = ldap
}
service imap-login {
client_limit = 1000
service_count = 0
inet_listener imaps {
port = 993
}
}
service managesieve-login {
inet_listener sieve {
port = 4190
}
}
service quota-warning {
executable = script ${quotaWarning}/bin/quota-warning.sh
unix_listener quota-warning {
user = vmail
group = vmail
mode = 0660
}
}
service quota-status {
# '-p <protocol>'. Currently only 'postfix' protocol is supported.
executable = quota-status -p postfix
client_limit = 1
inet_listener {
address = 127.0.0.1
port = 12340
}
}
protocol sieve {
managesieve_logout_format = bytes ( in=%i : out=%o )
}
plugin {
sieve_dir = /var/vmail/%d/%n/sieve/scripts/
sieve = /var/vmail/%d/%n/sieve/active-script.sieve
sieve_extensions = +vacation-seconds +editheader
sieve_vacation_min_period = 1min
fts = lucene
fts_lucene = whitespace_chars=@.
quota_warning = storage=100%% quota-warning 100 %u
quota_warning2 = storage=95%% quota-warning 95 %u
quota_warning3 = storage=90%% quota-warning 90 %u
quota_warning4 = storage=85%% quota-warning 85 %u
quota_grace = 10%%
quota_status_success = DUNNO
quota_status_nouser = DUNNO
quota_status_overquota = "552 5.2.2 Mailbox is full"
}
# If you have Dovecot v2.2.8+ you may get a significant performance improvement with fetch-headers:
imapc_features = $imapc_features fetch-headers
# Read multiple mails in parallel, improves performance
mail_prefetch_count = 20
'';
modules = [
pkgs.dovecot_pigeonhole
];
protocols = [
"sieve"
];
};
users.users.vmail = {
home = "/var/vmail";
createHome = true;
isSystemUser = true;
uid = 1000;
shell = "/run/current-system/sw/bin/nologin";
};
security.dhparams = {
enable = true;
params.dovecot2 = { };
};
sops.secrets.dovecot-ldap-password = {
sopsFile = ./openldap/secrets.yaml;
};
systemd.services.dovecot2.preStart = ''
sed -e "s/@ldap-password@/$(cat ${config.sops.secrets.dovecot-ldap-password.path})/" ${ldapConfig} > /run/dovecot2/ldap.conf
'';
systemd.services.dovecot2 = {
wants = [ "acme-imap.${domain}.service" ];
after = [ "acme-imap.${domain}.service" ];
};
users.groups.acme.members = [ "openldap" ];
/* trigger the actual certificate generation for your hostname */
security.acme.certs."imap.${domain}" = {
extraDomainNames = [
"imap-test.${domain}"
"imap-02.${domain}"
];
postRun = "systemctl restart dovecot2.service";
};
networking.firewall.allowedTCPPorts = [
143 # imap
993 # imaps
4190 # sieve
];
}

View File

@@ -0,0 +1,34 @@
{ pkgs, ... }:
{
virtualisation.docker.enable = true;
systemd.services.drone-runner = {
description = "Drone Server (CI CD Service)";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.docker ];
serviceConfig = {
# Type = "simple";
Name = "drone-runner";
User = "drone-server";
Group = "drone-server";
Restart = "always";
ExecStartPre= ''
-${pkgs.docker}/bin/docker stop %n \
-${pkgs.docker}/bin/docker rm %n \
${pkgs.docker}/bin/docker pull drone/drone:1
'';
ExecStart= ''
${pkgs.docker}/bin/docker run --rm --name %n \
--volume=/var/run/docker.sock:/var/run/docker.sock \
--env=DRONE_RPC_PROTO=https \
--env=DRONE_RPC_HOST=drone.cloonar.com \
--env=DRONE_RPC_SECRET=super-duper-secret \
--env=DRONE_RUNNER_CAPACITY=2 \
drone/drone-runner-docker:1
'';
};
};
}

View File

@@ -0,0 +1,57 @@
{ config, pkgs, ... }:
{
virtualisation.docker.enable = true;
users.users.drone-server = {
isSystemUser = true;
group = "drone-server";
home = "/var/lib/drone-server";
createHome = true;
};
users.groups.drone-server = { };
users.groups.docker.members = [ "drone-server" ];
systemd.services.drone-server = {
description = "Drone Server (CI CD Service)";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.docker ];
serviceConfig = {
# Type = "simple";
Name = "drone-server";
User = "drone-server";
Group = "drone-server";
Restart = "always";
ExecStartPre= ''
-${pkgs.docker}/bin/docker stop %n \
-${pkgs.docker}/bin/docker rm %n \
${pkgs.docker}/bin/docker pull drone/drone:1
'';
ExecStart= ''
${pkgs.docker}/bin/docker run --rm --name %n \
--env=DRONE_AGENTS_ENABLED=true \
--env=DRONE_GOGS_SERVER=https://git.cloonar.com \
--env=DRONE_GIT_ALWAYS_AUTH=true \
--env=DRONE_RPC_SECRET=super-duper-secret \
--env=DRONE_SERVER_HOST=drone.cloonar.com \
--env=DRONE_SERVER_PROTO=https \
--env=DRONE_USER_CREATE=username:dominik.polakovics,admin:true \
-v /var/lib/drone-server:/data \
--publish=8080:80 \
drone/drone:2
'';
};
};
services.nginx.enable = true;
services.nginx.virtualHosts."drone.cloonar.com" = {
forceSSL = true;
enableACME = true;
acmeRoot = null;
locations."/" = {
proxyPass = "http://localhost:8080";
};
};
}

View File

@@ -0,0 +1,49 @@
{ config, pkgs, ... }:
{
virtualisation.docker.enable = true;
users.users.drone-runner = {
isSystemUser = true;
group = "drone-runner";
home = "/var/lib/drone-runner";
createHome = true;
};
users.groups.drone-runner = { };
users.groups.docker.members = [ "drone-runner" ];
systemd.services.drone-runner = {
description = "Drone Runner (CI CD Service)";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.docker ];
serviceConfig = {
# Type = "simple";
Name = "drone-runner";
User = "drone-runner";
Group = "drone-runner";
Restart = "always";
ExecStartPre= ''
-${pkgs.docker}/bin/docker stop %n \
-${pkgs.docker}/bin/docker rm %n \
${pkgs.docker}/bin/docker pull drone/drone:1
'';
ExecStart= ''
${pkgs.docker}/bin/docker run --rm --name %n \
--volume=/var/run/docker.sock:/var/run/docker.sock \
--env-file=/run/secrets/drone-runner \
--env=DRONE_RPC_PROTO=https \
--env=DRONE_RPC_HOST=drone.cloonar.com \
--env=DRONE_RUNNER_CAPACITY=2 \
drone/drone-runner-docker:1
'';
};
};
sops.secrets.drone-runner = {
sopsFile = ./secrets.yaml;
owner = config.systemd.services.drone-runner.serviceConfig.User;
key = "drone";
};
}

View File

@@ -0,0 +1,30 @@
drone: ENC[AES256_GCM,data:Z1Rjso+5XYfvp2xJDXCQkI88GXl83v2oEkMLmOV/rb0DwRmhxCYzYX6fcdidk271Drf1YaPstVvm2LQB38jlBnJtg98aAGegj2fWfT44IbPIi8qDe93M2gFxFDgosoA2eOS2MjEwyBDp9GEUnKyi2gHR8khnTCvegVIntsusWOW/1tbzymKXavZAJUlX+82d/+6NWUEcnbislxhyph8P1Lgw546q,iv:SllCBHlq8ZCBqOHwMaCUcX6D/VDWsbN7uICZKb/R35w=,tag:mEb4E02VUaYGVjyI30FcXA==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age16veg3fmvpfm7a89a9fc8dvvsxmsthlm70nfxqspr6t8vnf9wkcwsvdq38d
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA0OW1JN0hjYjh4cDlmLyt6
dHRlSjN6Y1JWUFdzNWlZZ3c0Z2F4bXBCa1NFCjM3b3pPZVhtbDdob3lsR2xlMmJI
bjRRMHFjQ2kwWWJKT1p5VW5NVGJuZ3MKLS0tICtRcTFoSmxyeUhaaVlxQUxRWkJl
SXR2M293UFBxNFovRnlTQ1o4SzloaEEK+onGdd/7aEF71ibLoLXE5/SbJQWsKigh
h8BhfT1z9P5UYNoGHVv8Ry6LndyrBLEv+PUBuT0XJpEVPjKLm99KbQ==
-----END AGE ENCRYPTED FILE-----
- recipient: age106n5n3rrrss45eqqzz8pq90la3kqdtnw63uw0sfa2mahk5xpe30sxs5x58
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAyL3dDczRNMjNQUWVjelR5
TG93QUFjVGtMNFplaTErOTJjT2dHbWtWUVNzCjNTV0tUY2hpcnp1SDZ4UTB2aFNI
M2JwSkdNS0RFQVlPRUNzRG41aW5aS3cKLS0tIEJtaTRXdTI3NGJxZENJTk9jT1hi
N3RLRjdkMmZkSmZWZGlYbXRRUTJOZFEK2bJo7iyE3A5ds7tW5bAHgyfGqgH4cRjY
hLzYp083QYbXKAqP1w8a3JFXofv1RWd7tUb61I6R4Rd6hXZUv1a5Qw==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2023-02-10T12:35:53Z"
mac: ENC[AES256_GCM,data:44J9abLbHkvjAtIUqXVZlcEAnizgg5yxKwyaZhnqIzzebWEpzqcKP6b72blaD7/jSdAiUo7bk/m4BxKVGHf9XKGxyLastbgYoFtz40rsKg9LOKpEfO2kl3JV5dj7C1f8IgsHWZ8L3Vb6KFKcrK2bzjZ5K5p22hCze4lQbK7CZTE=,iv:TE+6juCOTjTrx5nQhi8W5gaZkMFYrEDtoPrGdSTJSNE=,tag:AVsCIkzPjtfk3uSlsv6Dlg==,type:str]
pgp: []
unencrypted_suffix: _unencrypted
version: 3.7.3

View File

@@ -0,0 +1,64 @@
{ config, pkgs, ... }:
{
virtualisation.docker.enable = true;
users.users.drone-server = {
isSystemUser = true;
group = "drone-server";
home = "/var/lib/drone-server";
createHome = true;
};
users.groups.drone-server = { };
users.groups.docker.members = [ "drone-server" ];
systemd.services.drone-server = {
description = "Drone Server (CI CD Service)";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.docker ];
serviceConfig = {
# Type = "simple";
Name = "drone-server";
User = "drone-server";
Group = "drone-server";
Restart = "always";
ExecStartPre= ''
-${pkgs.docker}/bin/docker stop %n \
-${pkgs.docker}/bin/docker rm %n \
${pkgs.docker}/bin/docker pull drone/drone:1
'';
ExecStart= ''
${pkgs.docker}/bin/docker run --rm --name %n \
--env-file=/run/secrets/drone-server \
--env=DRONE_AGENTS_ENABLED=true \
--env=DRONE_GITEA_SERVER=https://git.cloonar.com \
--env=DRONE_GITEA_CLIENT_ID=6a7b8c57-bd71-49c8-b67d-c2de68fda649 \
--env=DRONE_GIT_ALWAYS_AUTH=true \
--env=DRONE_SERVER_HOST=drone.cloonar.com \
--env=DRONE_SERVER_PROTO=https \
--env=DRONE_USER_CREATE=username:dominik.polakovics,admin:true \
-v /var/lib/drone:/data \
--publish=8080:80 \
drone/drone:2
'';
};
};
services.nginx.enable = true;
services.nginx.virtualHosts."drone.cloonar.com" = {
forceSSL = true;
enableACME = true;
acmeRoot = null;
locations."/" = {
proxyPass = "http://localhost:8080";
};
};
sops.secrets.drone-server = {
sopsFile = ./secrets.yaml;
owner = config.systemd.services.drone-server.serviceConfig.User;
key = "drone";
};
}

25
utils/modules/gitea.nix Normal file
View File

@@ -0,0 +1,25 @@
{ config, ... }:
let
domain = "git.cloonar.com";
in
{
services.nginx.virtualHosts."${domain}" = {
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "http://localhost:3001/";
};
};
services.gitea = {
enable = true;
appName = "Cloonar Gitea server"; # Give the site a name
domain = domain;
rootUrl = "https://${domain}/";
httpPort = 3001;
settings = {
service.DISABLE_REGISTRATION = true;
webhook.ALLOWED_HOST_LIST = "drone.cloonar.com";
};
};
}

28
utils/modules/gnome.nix Normal file
View File

@@ -0,0 +1,28 @@
{ config, pkgs, ... }:
{
hardware.pulseaudio.enable = false;
services.xserver = {
enable = true;
libinput.enable = true;
displayManager.gdm.enable = true;
displayManager.defaultSession = "sway";
desktopManager.gnome = {
enable = true;
extraGSettingsOverrides = ''
[org.gnome.desktop.interface]
gtk-theme='Dracula'
'';
};
};
environment.systemPackages = with pkgs; [
dracula-theme
gnome.gnome-tweaks
gnome.dconf-editor
gnomeExtensions.vitals
gnomeExtensions.forge
];
}

37
utils/modules/gogs.nix Normal file
View File

@@ -0,0 +1,37 @@
{ config, ... }:
{
services.gogs = {
enable = true;
domain = "git.cloonar.com";
rootUrl = "http://git.cloonar.com/";
httpAddress = "git.cloonar.com";
httpPort = 3000;
extraConfig = ''
[server]
EXTERNAL_URL = http://git.cloonar.com/
[auth]
DISABLE_REGISTRATION = true
[security]
# specific network address, separated by commas, no port needed
LOCAL_NETWORK_ALLOWLIST = drone.cloonar.com git-2.cloonar.com
'';
};
services.nginx.enable = true;
services.nginx.virtualHosts."git.cloonar.com" = {
forceSSL = true;
enableACME = true;
acmeRoot = null;
locations."/" = {
proxyPass = "http://git.cloonar.com:3000";
proxyWebsockets = true;
extraConfig =
"proxy_connect_timeout 300;" +
"proxy_send_timeout 300;" +
"proxy_read_timeout 300;" +
"send_timeout 300;"
;
};
};
}

View File

@@ -0,0 +1,15 @@
{
services.gogs = {
enable = true;
domain = "git.cloonar.com";
rootUrl = "http://git.cloonar.com/";
httpAddress = "git.cloonar.com";
httpPort = 3000;
extraConfig = ''
[server]
EXTERNAL_URL = http://git.cloonar.com/
[auth]
DISABLE_REGISTRATION = true
'';
};
}

View File

@@ -0,0 +1,103 @@
{
services.home-assistant.extraComponents = [
"daikin"
];
services.home-assistant.config = {
sensor = [
{
name = "Living Room Window Handle";
platform = "enocean";
id = [ 129 0 227 53 ];
device_class = "windowhandle";
}
];
"automation ac_livingroom" = {
alias = "ac_livingroom";
hide_entity = true;
trigger = {
platform = "state";
entity_id = "sensor.windowhandle_living_room_window_handle";
to = [ "open" "tilt" ];
};
action = {
service = "climate.set_hvac_mode";
target = {
entity_id = "climate.livingroom_ac";
};
data = {
hvac_mode = "off";
};
};
};
"automation ac_eco" = {
alias = "ac_eco";
hide_entity = true;
trigger = {
platform = "state";
entity_id = [
"climate.livingroom_ac"
"climate.bedroom_ac"
];
to = [
"heat"
"cold"
];
};
action = {
service = "climate.set_preset_mode";
target = {
entity_id = "{{ trigger.entity_id }}";
};
data = {
preset_mode = "eco";
};
};
};
"automation bedroom_ac_on" = {
alias = "bedroom ac on";
hide_entity = true;
trigger = {
platform = "time";
at = "00:30:00";
};
action = {
choose = [
{
conditions = [ "{{ states('sensor.bedroom_ac_inside_temperature') > 25 and states('sensor.bedroom_ac_outside_temperature') > 22 }}" ];
sequence = [
{
service = "climate.set_hvac_mode";
target = {
entity_id = "climate.bedroom_ac";
};
data = {
hvac_mode = "cold";
};
}
];
}
];
};
};
"automation bedroom_ac_off" = {
alias = "bedroom ac on";
hide_entity = true;
trigger = {
platform = "template";
value_template = ''
{{ now().timestamp() | timestamp_custom('%H:%M') == (as_timestamp(strptime(states('sensor.bedtime_alarm'), "%H:%M")) - 1800) | timestamp_custom('%H:%M', false) }}
'';
};
action = {
service = "climate.set_hvac_mode";
target = {
entity_id = "climate.bedroom_ac";
};
data = {
hvac_mode = "off";
};
};
};
};
}

View File

@@ -0,0 +1,31 @@
{ pkgs, ... }:
{
services.home-assistant.package = pkgs.home-assistant.override {
extraPackages = ps: with ps; [ pyelectroluxconnect ];
packageOverrides = self: super: {
pyelectroluxconnect = super.buildPythonPackage rec {
pname = "pyelectroluxconnect";
version = "0.3.12";
src = super.fetchPypi {
inherit pname version;
sha256 = "sha256-g9UxkWuTIqJe0/CDk3kwU3dSmc+GXlfDMxdzu6CqyY0=";
};
doCheck = false;
};
};
};
# services.home-assistant.extraPackages = python3Packages: with python3Packages; [
# (callPackage ../../pkgs/pyelectroluxconnect.nix)
# ];
services.home-assistant.config = {
electrolux_status = {
username = "dominik@superbros.tv";
password = "U26tTTYtXdhErWpbRxRRVZy541vFvWyn";
region = "emea";
};
};
}

View File

@@ -0,0 +1,91 @@
{
services.home-assistant.config = {
sensor = [
{
platform = "template";
sensors = {
sensors_lowest_battery_level = {
friendly_name = "Lowest battery level (Sensors)";
entity_id = "sun.sun";
device_class = "battery";
unit_of_measurement = "%";
value_template = ''
{% set domains = ['sensor', 'battery'] %}
{% set ns = namespace(min_batt=100, entities=[]) %}
{%- set exclude_sensors = ['sensor.sensors_lowest_battery_level','sensor.dominiks_iphone_battery_level'] -%}
{% for domain in domains %}
{% set ns.entities = states[domain] %}
{% for sensor in exclude_sensors %}
{% set ns.entities = ns.entities | rejectattr('entity_id', 'equalto', sensor) %}
{% endfor %}
{% set batt_sensors = ns.entities | selectattr('attributes.device_class','equalto','battery') | map(attribute='state') | reject('equalto', 'unknown') | reject('equalto', 'None') | map('int') | reject('equalto', 0) | list %}
{% set batt_attrs = ns.entities | selectattr('attributes.battery_level','defined') | map(attribute='attributes.battery_level') | reject('equalto', 'unknown') | reject('equalto', 'None') | map('int') | reject('equalto', 0) | list %}
{% set batt_lvls = batt_sensors + batt_attrs %}
{% if batt_lvls|length > 0 %}
{% set _min = batt_lvls|min %}
{% if _min < ns.min_batt %}
{% set ns.min_batt = _min %}
{% endif %}
{% endif %}
{% endfor %}
{{ ns.min_batt }}
'';
};
};
}
];
binary_sensor = [
{
platform = "template";
sensors = {
sensor_low_battery = {
value_template = "{{ states('sensor.sensors_lowest_battery_level')|int <= 30 }}";
friendly_name = "A sensor has low battery";
device_class = "problem";
};
};
}
];
alert = {
sensor_low_battery = {
name = "Sensor has low battery!";
message = ''
{%- set domains = ['sensor', 'battery'] -%}
{%- set threshold = 30 -%}
{%- set exclude_entities = ['sensor.sensors_lowest_battery_level','sensor.dominiks_iphone_battery_level'] -%}
Sensors are below 50% battery:
{%- for domain in domains -%}
{%- for item in states[domain] -%}
{%- if item.entity_id not in exclude_entities -%}
{%- if item.attributes.battery_level is defined -%}
{%- set level = item.attributes.battery_level|int -%}
{% if level > 0 and level < threshold %}
- {{ item.attributes.friendly_name }} ({{ item.attributes['battery_level']|int}}%)
{%- endif -%}
{%- endif -%}
{%- if item.attributes.device_class is defined and item.attributes.device_class == 'battery' -%}
{%- set level = item.state|int -%}
{% if level > 0 and level <= threshold %}
- {{ item.attributes.friendly_name }} ({{ item.state|int }}%)
{%- endif -%}
{%- endif %}
{%- endif -%}
{%- endfor -%}
{%- endfor -%}
'';
entity_id = "binary_sensor.sensor_low_battery";
state = "on";
repeat = [
5
60
360
];
skip_first = true;
can_acknowledge = true;
notifiers = [
"NotificationGroup"
];
};
};
};
}

View File

@@ -0,0 +1,128 @@
{ pkgs, ... }: {
imports = [
./ac.nix
# ./aeg.nix
./battery.nix
./ecovacs.nix
./enocean.nix
./ldap.nix
./light.nix
./locks.nix
./multimedia.nix
./notify.nix
./pc.nix
./presence.nix
./pushover.nix
./scene-switch.nix
./sleep.nix
./snapcast.nix
];
services.home-assistant = {
enable = true;
};
services.home-assistant.extraComponents = [
"mobile_app"
"shopping_list"
"backup"
"denonavr"
"androidtv"
"rainbird"
];
services.home-assistant.config =
let
hiddenEntities = [
"sensor.last_boot"
"sensor.date"
];
in
{
homeassistant = {
name = "Home";
latitude = "!secret home_latitude";
longitude = "!secret home_longitude";
# elevation = "!secret home_elevation";
unit_system = "metric";
time_zone = "Europe/Vienna";
country = "AT";
};
automation = "!include automations.yaml";
frontend = { };
http = {
use_x_forwarded_for = true;
trusted_proxies = [
"127.0.0.1"
"::1"
];
};
history.exclude = {
entities = hiddenEntities;
domains = [
"automation"
"updater"
];
};
"map" = { };
enocean = {
device = "/dev/serial/by-id/usb-EnOcean_GmbH_EnOcean_USB_300_DC_FT5OI9YG-if00-port0";
};
# logbook.exclude.entities = "hiddenEntities";
logger = {
default = "info";
};
#icloud = {
# username = "!secret icloud_email";
# password = "!secret icloud_password";
# with_family = true;
#};
network = { };
zeroconf = { };
system_health = { };
default_config = { };
system_log = { };
sensor = [
{
platform = "template";
sensors.bedtime_alarm = {
friendly_name = "Bedtime Alarm";
value_template = "09:00";
};
}
];
};
services.nginx.virtualHosts."home-assistant.cloonar.com" = {
forceSSL = true;
enableACME = true;
acmeRoot = null;
extraConfig = ''
proxy_buffering off;
'';
locations."/".extraConfig = ''
proxy_pass http://127.0.0.1:8123;
proxy_set_header Host $host;
proxy_redirect http:// https://;
proxy_http_version 1.1;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
'';
};
sops.secrets."home-assistant-secrets.yaml" = {
sopsFile = ./secrets.yaml;
owner = "hass";
path = "/var/lib/hass/secrets.yaml";
restartUnits = [ "home-assistant.service" ];
};
users.users.hass.extraGroups = [ "dialout" ];
networking.firewall = {
allowedUDPPorts = [ 5683 ];
};
}

View File

@@ -0,0 +1,5 @@
{
services.home-assistant.extraComponents = [
"ecovacs"
];
}

View File

@@ -0,0 +1,12 @@
{
services.home-assistant.config = {
"binary_sensor pc_0" = [
{
platform = "enocean";
id = [ 254 235 105 198 ];
name = "enocean_switch_pc";
}
];
logger.logs."homeassistant.components.enocean" = "debug";
};
}

View File

@@ -0,0 +1,62 @@
{ pkgs
, config
, lib
, ... }:
let
ldap-auth-sh = pkgs.stdenv.mkDerivation {
name = "ldap-auth-sh";
src = pkgs.fetchFromGitHub {
owner = "efficiosoft";
repo = "ldap-auth-sh";
rev = "93b2c00413942908139e37c7432a12bcb705ac87";
sha256 = "1pymp6ki353aqkigr89g7hg5x1mny68m31c3inxf1zr26n5s2kz8";
};
nativeBuildInputs = [ pkgs.makeWrapper ];
installPhase = ''
mkdir -p $out/etc
cat > $out/etc/home-assistant.cfg << 'EOF'
CLIENT="ldapsearch"
SERVER="ldaps://ldap.cloonar.com:636"
USERDN="cn=home-assistant,ou=system,ou=users,dc=cloonar,dc=com"
PW="$(<${config.sops.secrets.home-assistant-ldap.path})"
BASEDN="ou=users,dc=cloonar,dc=com"
SCOPE="one"
FILTER="(&(objectClass=cloonarUser)(memberOf=cn=HomeAssistant,ou=groups,dc=cloonar,dc=com)(mail=$(ldap_dn_escape "$username")))"
USERNAME_PATTERN='^[a-z|A-Z|0-9|_|-|.|@]+$'
on_auth_success() {
# print the meta entries for use in HA
if echo "$output" | grep -qE '^(dn|DN):: '; then
# ldapsearch base64 encodes non-ascii
output=$(echo "$output" | sed -n -e "s/^\(dn\|DN\)\s*::\s*\(.*\)$/\2/p" | base64 -d)
else
output=$(echo "$output" | sed -n -e "s/^\(dn\|DN\)\s*:\s*\(.*\)$/\2/p")
fi
name=$(echo "$output" | sed -nr 's/^cn=([^,]+).*/\1/Ip')
[ -z "$name" ] || echo "name=$name"
}
EOF
install -D -m755 ldap-auth.sh $out/bin/ldap-auth.sh
wrapProgram $out/bin/ldap-auth.sh \
--prefix PATH : ${lib.makeBinPath [pkgs.openldap pkgs.coreutils pkgs.gnused pkgs.gnugrep]} \
--add-flags "$out/etc/home-assistant.cfg"
'';
};
in
{
services.home-assistant.config.homeassistant.auth_providers = [
{
type = "command_line";
command = "${ldap-auth-sh}/bin/ldap-auth.sh";
meta = true;
}
];
sops.secrets.home-assistant-ldap = {
sopsFile = ./secrets.yaml;
owner = "hass";
};
}

View File

@@ -0,0 +1,335 @@
{
services.home-assistant.extraComponents = [
"deconz"
"shelly"
"sun"
];
services.home-assistant.config = {
homeassistant = {
customize_domain = {
light = {
assumed_state = false;
};
};
};
"automation light_sunrise" = {
alias = "light_sunrise";
hide_entity = true;
trigger = {
platform = "sun";
event = "sunrise";
};
action = {
service = "light.turn_on";
target = {
entity_id = "{{ states.light | selectattr(\"state\",\"eq\",\"on\") | map(attribute=\"entity_id\") | list }}";
};
data = {
brightness_pct = 254;
color_temp = 250;
};
};
};
"automation light_sunset" = {
alias = "light_sunset";
hide_entity = true;
trigger = {
platform = "sun";
event = "sunset";
};
action = {
service = "light.turn_on";
target = {
entity_id = "{{ states.light | selectattr(\"state\",\"eq\",\"on\") | map(attribute=\"entity_id\") | list }}";
};
data = {
brightness_pct = 30;
color_temp = 450;
};
};
};
"automation light_on" = {
alias = "light_on";
hide_entity = true;
trigger = {
platform = "state";
entity_id = [
"light.bed_room"
"light.kitchen"
"light.livingroom_lights"
"light.hallway_lights"
"light.bathroom_light"
"light.toilett_lights"
"light.storage_lights"
];
to = "on";
};
action = [
{
choose = [
{
conditions = [ "{{ state_attr('sun.sun', 'elevation') < 5 and trigger.entity_id == 'light.toilett_lights' }}" ];
sequence = [
{
service = "light.turn_on";
target = {
entity_id = "{{ trigger.entity_id }}";
};
data = {
brightness_pct = 30;
color_temp = 450;
};
}
];
}
{
conditions = [ "{{ state_attr('sun.sun', 'elevation') < 5 and trigger.entity_id == 'light.hallway_lights' }}" ];
sequence = [
{
service = "light.turn_on";
target = {
entity_id = "{{ trigger.entity_id }}";
};
data = {
brightness_pct = 1;
color_temp = 450;
};
}
];
}
{
conditions = [ "{{ state_attr('sun.sun', 'elevation') < 5 and trigger.entity_id == 'light.bathroom_light' }}" ];
sequence = [
{
service = "light.turn_on";
target = {
entity_id = "{{ trigger.entity_id }}";
};
data = {
brightness_pct = 30;
color_temp = 450;
};
}
];
}
{
conditions = [ "{{ state_attr('sun.sun', 'elevation') < 5 and trigger.entity_id == 'light.livingroom_lights' }}" ];
sequence = [
{
service = "light.turn_on";
target = {
entity_id = "{{ trigger.entity_id }}";
};
data = {
brightness_pct = 5;
color_temp = 450;
};
}
];
}
{
conditions = [ "{{ state_attr('sun.sun', 'elevation') < 5 and state_attr(trigger.entity_id, 'is_deconz_group') != None }}" ];
sequence = [
{
service = "light.turn_on";
target = {
entity_id = "{{ trigger.entity_id }}";
};
data = {
brightness_pct = 30;
color_temp = 450;
};
}
];
}
{
conditions = [ "{{ state_attr('sun.sun', 'elevation') > 4 }}" ];
sequence = [
{
service = "light.turn_on";
target = {
entity_id = "{{ trigger.entity_id }}";
};
data = {
brightness_pct = 100;
color_temp = 250;
};
}
];
}
];
}
];
};
"automation bathroom light small" = {
alias = "bathroom light small";
mode = "restart";
hide_entity = true;
trigger = {
platform = "state";
entity_id = [
"light.bathroom_switch_channel_1"
];
from = "on";
to = "off";
};
action = [
{
service = "switch.turn_off";
target = {
entity_id = "switch.bathroom_small";
};
}
];
};
"automation bathroom light" = {
alias = "bathroom light";
mode = "restart";
hide_entity = true;
trigger = {
platform = "state";
entity_id = [
"light.bathroom_switch_channel_1"
];
from = "off";
to = "on";
};
action = [
{
delay = 3600;
}
{
service = "light.turn_off";
target = {
entity_id = "light.bathroom_switch_channel_1";
};
}
];
};
"automation bed_led" = {
alias = "bed_led";
mode = "restart";
hide_entity = true;
trigger = {
platform = "state";
entity_id = [
"light.bedroom_led"
];
from = "off";
to = "on";
};
action = [
{
delay = 10800;
}
{
service = "light.turn_off";
target = {
entity_id = "{{ trigger.entity_id }}";
};
}
];
};
"automation hallway_motion" = {
alias = "Hallway Motion";
hide_entity = true;
trigger = {
platform = "state";
entity_id = "binary_sensor.hallway_motion_motion";
};
action = {
service_template = "light.turn_{{ trigger.to_state.state }}";
target = {
entity_id = "light.hallway_lights";
};
};
};
"automation bed_button_1" = {
alias = "bed_button_1";
trigger = {
platform = "event";
event_type = "shelly.click";
event_data = {
device = "shellybutton1-E8DB84AA196D";
};
};
action = [
{
choose = [
{
conditions = [ "{{ trigger.event.data.click_type == \"single\" }}" ];
sequence = [
{
service = "light.toggle";
entity_id = "light.bed_reading_1";
}
];
}
{
conditions = [ "{{ trigger.event.data.click_type == \"double\" }}" ];
sequence = [
{
service = "light.toggle";
entity_id = "light.bedroom_lights";
}
];
}
{
conditions = [ "{{ trigger.event.data.click_type == \"triple\" }}" ];
sequence = [
{
service = "light.toggle";
entity_id = "light.bedroom_bed";
}
];
}
];
}
];
};
"automation bed_button_2" = {
alias = "bed_button_2";
trigger = {
platform = "event";
event_type = "shelly.click";
event_data = {
device = "shellybutton1-E8DB84AA136D";
};
};
action = [
{
choose = [
{
conditions = [ "{{ trigger.event.data.click_type == \"single\" }}" ];
sequence = [
{
service = "light.toggle";
entity_id = "light.bed_reading_2";
}
];
}
{
conditions = [ "{{ trigger.event.data.click_type == \"double\" }}" ];
sequence = [
{
service = "light.toggle";
entity_id = "light.bedroom_lights";
}
];
}
{
conditions = [ "{{ trigger.event.data.click_type == \"triple\" }}" ];
sequence = [
{
service = "light.toggle";
entity_id = "light.bedroom_bed";
}
];
}
];
}
];
};
};
}

View File

@@ -0,0 +1,117 @@
{
services.home-assistant.extraComponents = [
"nuki"
];
services.home-assistant.config = {
"automation house_door" = {
alias = "house_door";
mode = "restart";
hide_entity = true;
trigger = {
platform = "state";
entity_id = [
"person.dominik"
];
from = "not_home";
to = "home";
};
action = [
{
service = "lock.unlock";
target = {
entity_id = "lock.house_door";
};
}
{
delay = "00:05:00";
}
{
service = "lock.lock";
target = {
entity_id = "lock.house_door";
};
}
];
};
"automation house_door_ring" = {
alias = "house_door_ring";
trigger = {
platform = "event";
event_type = "nuki_event";
event_data = {
type = "ring";
};
};
action = [
{
choose = [
{
conditions = [ "{{ state.house_door == \"unlocked\" }}" ];
sequence = [
{
service = "lock.lock";
target = {
entity_id = "lock.house_door";
};
}
];
}
];
}
];
};
binary_sensor = [
{
platform = "template";
sensors = {
lock_critical_battery = {
value_template = ''
{% set domains = ['lock'] %}
{% set ns = namespace(crit=battery_critical, entities=[]) %}
{% for domain in domains %}
{% set batt_critical = states[domain] | selectattr('attributes.battery_critical','defined') | map(attribute='attributes.battery_critical') | reject('equalto', 'unknown') | reject('equalto', 'None') | map('int') | reject('equalto', 0) | list %}
{% if batt_critical|length > 0 %}
{% set ns.battery_critical = true %}
{% endif %}
{% endfor %}
{{ ns.battery_critical }}
'';
friendly_name = "A lock has critical battery";
device_class = "problem";
};
};
}
];
alert = {
battery_critical = {
name = "Lock has low battery!";
message = ''
{%- set domains = ['lock'] -%}
Lock battery is critical:
{%- for domain in domains -%}
{%- for item in states[domain] -%}
{%- if item.attributes.battery_critical is defined -%}
{% if item.attributes.battery_critical %}
- {{ item.attributes.friendly_name }}
{%- endif -%}
{%- endif -%}
{%- endfor -%}
{%- endfor -%}
'';
entity_id = "binary_sensor.lock_critical_battery";
state = "on";
repeat = [
5
60
360
];
skip_first = true;
can_acknowledge = true;
notifiers = [
"NotificationGroup"
];
};
};
};
}

View File

@@ -0,0 +1,253 @@
{
services.home-assistant.config = {
binary_sensor = [
{
name = "ps5_living";
platform = "command_line";
command = "python /var/lib/hass/ps5.py -q -b 10.42.96.176";
device_class = "connectivity";
scan_interval = 5;
}
{
platform = "template";
sensors = {
multimedia_device_on = {
friendly_name = "Any multimedia device on";
device_class = "connectivity";
value_template = ''
{% if is_state('binary_sensor.ps5_living', 'on') or states('media_player.fire_tv_firetv_living_cloonar_com') != 'off' or states('device_tracker.xbox') == 'home' %}
on
{% else %}
off
{% endif %}
'';
};
};
}
];
# "automation tv scene" = {
# alias = "auto tv scene";
# hide_entity = true;
# trigger = {
# platform = "event";
# event_type = "button_pressed";
# event_data = {
# id = [ 254 235 105 198 ];
# };
# };
# action = {
# service_template = "switch.turn_on";
# data_template = {
# entity_id = "switch.computer";
# };
# };
# };
# "automation beamer switch" = {
# alias = "auto beamer scene";
# hide_entity = true;
# trigger = {
# platform = "state";
# entity_id = "sensor.computer_power";
# };
# condition = {
# condition = "and";
# conditions = [
# {
# condition = "numeric_state";
# entity_id = "sensor.computer_power";
# below = 15;
# }
# "{{ (as_timestamp(now()) - as_timestamp(states.switch.computer.last_changed)) > 300 }}"
# ];
# };
# action = {
# service = "switch.turn_off";
# target = {
# entity_id = [ "switch.computer" ];
# };
# };
# };
"automation xbox on" = {
alias = "xbox on";
hide_entity = true;
trigger = {
platform = "state";
entity_id = "device-tracker.xbox";
to = "home";
};
action = [
{
service = "denonavr.get_command";
target = {
entity_id = "media_player.marantz_sr6015";
};
data = {
command = "/goform/formiPhoneAppDirect.xml?SIGAME";
};
}
{
delay = 5;
}
{
service = "denonavr.get_command";
target = {
entity_id = "media_player.marantz_sr6015";
};
data = {
command = "/goform/formiPhoneAppDirect.xml?PWSTANDBY";
};
}
];
};
"automation firetv on" = {
alias = "firetv on";
hide_entity = true;
trigger = {
platform = "state";
entity_id = "media_player.fire_tv_firetv_living_cloonar_com";
from = "off";
};
action = [
{
service = "denonavr.get_command";
target = {
entity_id = "media_player.marantz_sr6015";
};
data = {
command = "/goform/formiPhoneAppDirect.xml?SIMPLAY";
};
}
{
delay = 5;
}
{
service = "denonavr.get_command";
target = {
entity_id = "media_player.marantz_sr6015";
};
data = {
command = "/goform/formiPhoneAppDirect.xml?PWSTANDBY";
};
}
];
};
"automation ps5 on" = {
alias = "ps5 on";
hide_entity = true;
trigger = {
platform = "state";
entity_id = "binary_sensor.ps5_living";
to = "on";
};
action = [
{
service = "denonavr.get_command";
target = {
entity_id = "media_player.marantz_sr6015";
};
data = {
command = "/goform/formiPhoneAppDirect.xml?SIBD";
};
}
{
delay = 5;
}
{
service = "denonavr.get_command";
target = {
entity_id = "media_player.marantz_sr6015";
};
data = {
command = "/goform/formiPhoneAppDirect.xml?PWSTANDBY";
};
}
];
};
"automation all multimedia off" = {
alias = "all multimedia off";
trigger = {
platform = "state";
entity_id = "binary_sensor.multimedia_device_on";
to = "off";
};
action = [
{
service = "androidtv.adb_command";
target = {
device_id = "a5e50f268f3a2dbd0741fb8e9ff7f931";
};
data = {
command = "POWER";
};
}
{
service = "denonavr.get_command";
target = {
entity_id = "media_player.marantz_sr6015";
};
data = {
command = "/goform/formiPhoneAppDirect.xml?PWSTANDBY";
};
}
];
};
"automation all_multimedia_on" = {
alias = "all multimedia on";
trigger = {
platform = "state";
entity_id = "binary_sensor.multimedia_device_on";
to = "on";
};
condition = {
condition = "or";
conditions = [
{
condition = "state";
entity_id = "media_player.android_tv_metz_cloonar_com";
state = "off";
}
{
condition = "state";
entity_id = "media_player.android_tv_metz_cloonar_com";
state = "unavailable";
}
];
};
action = [
{
service = "androidtv.adb_command";
target = {
device_id = "a5e50f268f3a2dbd0741fb8e9ff7f931";
};
data = {
command = "POWER";
};
}
];
};
# "automation multimedia input" = {
# hide_entity = true;
# trigger = {
# platform = "state";
# entity_id = "sensor.computer_power";
# };
# condition = {
# condition = "and";
# conditions = [
# {
# condition = "numeric_state";
# entity_id = "sensor.computer_power";
# below = 15;
# }
# "{{ (as_timestamp(now()) - as_timestamp(states.switch.computer.last_changed)) > 300 }}"
# ];
# };
# action = {
# service = "switch.turn_off";
# target = {
# entity_id = [ "switch.computer" ];
# };
# };
# };
};
}

View File

@@ -0,0 +1,65 @@
{
services.home-assistant.extraComponents = [
"nuki"
];
services.home-assistant.config = {
"automation house_door" = {
alias = "house_door";
mode = "restart";
hide_entity = true;
trigger = {
platform = "state";
entity_id = [
"person.dominik"
];
from = "not_home";
to = "home";
};
action = [
{
service = "lock.unlock";
target = {
entity_id = "lock.house_door";
};
}
{
delay = "00:05:00";
}
{
service = "lock.lock";
target = {
entity_id = "lock.house_door";
};
}
];
};
"automation house_door_ring" = {
alias = "house_door_ring";
trigger = {
platform = "event";
event_type = "nuki_event";
event_data = {
type = "ring";
};
};
action = [
{
choose = [
{
conditions = [ "{{ state.house_door == \"unlocked\" }}" ];
sequence = [
{
service = "lock.lock";
target = {
entity_id = "lock.house_door";
};
}
];
}
];
}
];
};
};
}

View File

@@ -0,0 +1,15 @@
{
services.home-assistant.config = {
notify = [
{
name = "NotificationGroup";
platform = "group";
services = [
{
service = "pushover_dominik";
}
];
}
];
};
}

View File

@@ -0,0 +1,46 @@
{
services.home-assistant.config = {
"automation pc_switch" = {
alias = "switch pc";
hide_entity = true;
trigger = {
platform = "event";
event_type = "button_pressed";
event_data = {
id = [ 254 235 105 198 ];
};
};
action = {
service_template = "switch.turn_on";
data_template = {
entity_id = "switch.computer";
};
};
};
"automation pc power" = {
alias = "auto pc power off";
hide_entity = true;
trigger = {
platform = "state";
entity_id = "sensor.computer_power";
};
condition = {
condition = "and";
conditions = [
{
condition = "numeric_state";
entity_id = "sensor.computer_power";
below = 15;
}
"{{ (as_timestamp(now()) - as_timestamp(states.switch.computer.last_changed)) > 300 }}"
];
};
action = {
service = "switch.turn_off";
target = {
entity_id = [ "switch.computer" ];
};
};
};
};
}

View File

@@ -0,0 +1,23 @@
{
services.home-assistant.extraComponents = [
"mqtt_room"
"opnsense"
];
services.home-assistant.config = {
opnsense = {
url = "https://fw.cloonar.com/api";
api_secret = "!secret opnsense_api_secret";
api_key = "!secret opnsense_api_key";
};
sensor = [
{
platform = "mqtt_room";
name = "Dominiks iPhone BLE";
device_id = "roomAssistant:d2a41d13-16bf-41fb-af4b-c520bdc7b68a";
# device_id = "0a666fe0ccd0d587414fec9b9946168f";
state_topic = "espresense/rooms";
away_timeout = 30;
}
];
};
}

View File

@@ -0,0 +1,37 @@
{ config, ... }:
{
config.sops.secrets.ps5-mqtt-env = {
sopsFile = ./secrets.yaml;
restartUnits = [ "podman-ps5Mqtt.service" ];
};
config.virtualisation.oci-containers.containers = {
ps5Mqtt = {
image = "ghcr.io/funkeyflo/ps5-mqtt/amd64:latest ";
# ports = ["127.0.0.1:8645:8645"];
volumes = [
"/var/lib/ps5-mqtt:/config"
];
# entrypoint = "/config/run.sh";
entrypoint = "/usr/bin/node";
cmd = [
"app/server/dist/index.js"
];
# entrypoint = "/bin/bash";
# cmd = [
# "-c \"echo $MQTT_HOST\""
# ];
environmentFiles = [
config.sops.secrets.ps5-mqtt-env.path
];
extraOptions = [
"--network=host"
];
};
};
config.networking.firewall = {
enable = true;
allowedTCPPorts = [ 8645 ];
};
}

View File

@@ -0,0 +1,16 @@
{
services.home-assistant.extraComponents = [
"pushover"
];
# services.home-assistant.config = {
# notify = [
# {
# name = "pushover_dominik";
# platform = "pushover";
# api_key = "!secret pushover_dominik_api_key";
# user_key = "!secret pushover_dominik_user_key";
# }
# ];
# };
}

View File

@@ -0,0 +1,21 @@
{
services.home-assistant.config = {
"automation scene_switch" = {
alias = "switch scene";
hide_entity = true;
trigger = {
platform = "event";
event_type = "button_pressed";
event_data = {
id = [ 254 242 234 134 ];
};
};
action = {
service_template = "switch.turn_on";
data_template = {
entity_id = "switch.computer";
};
};
};
};
}

View File

@@ -0,0 +1,32 @@
home-assistant-ldap: ENC[AES256_GCM,data:De7kuRji+flc0juqE3z1MyNo938Y18jhYtxHkvBX1AsmcJO6a37qJ6o9eCVyhcN/uyaS8cySucqKErh3SlCOZw==,iv:DD4Bp7yU0TCQ/Zeildmrt1HSUbuJgQ0L2UUSmMi6Obo=,tag:t1Hz0GiXzodVK64e0Xuf6g==,type:str]
home-assistant-secrets.yaml: ENC[AES256_GCM,data:owCBlfPXA66zxGaMwo6mViSZS7f3WPXOTtWiWFR8R/9Cqv7YvIaY7cqYX1OYFfEJyMyq6YLhVV9Zi0/K40VtUmhgR324x/knQbKkZGxv6gwqdIZ/zaZgQIu29xtY4DQnK24/5942HtisSnQnJxgbpchnjJCAQqri2/68LjGh8GoJVhF34Zji0MHBLAxR1y5JApOb5GUpc0ftMu4j6cbU/qxiZbRkzbrjlgAjBFjARYmsaWiilXg5jT2pf0Fz6bOHslcO+b3qePgIb/cJPP9aVYy76QA0oZ03f3Qu0w+IsjBKuXGaWKVBWBDyK1E37Y/Xif8w3H3cPqkAI6qxCPXi7djYfEpOz2M1L+5GTJGJz8fmvYzVJqU+hpZeI/7qJdiQ4/98YfuDU6nTEwWumcsPKsafJzdSeVfFYcE2x1H+QJDfJ3sgWJkfLDOrRxaXIwoXeSBSYAXPxjdomBzcdgKvo8MBH47pDbR4hK+y8+0LnoPafIyh8+FeAFzbn6/9ScWzF/MOyxFnap3edd12k5yWN+Yc4xacye3RR1h/mp1+DQF9xZ1xMTE70nwNdtIsGU0cV8qUvPgknZB/0US2oZ19fdxVuAN2rneEkK/nIGoKVw==,iv:x/F8CnsxROweCosvX1yAMHzwtI34kGauPvWF8yu2Yf8=,tag:WbzjtVhEPgUhL8g07kHjdA==,type:str]
ps5-mqtt-env: ENC[AES256_GCM,data:g079HmYjMQ/Dr/vHSuxnLDwyOG3bSmzGtUfQLXJgKFUoC+5dAyUwYu8WRD1hskER2v8yIz2oHx8dQXLuWsmKYRErk6Sybpe0+FOcqOvAXgzv1ow95sjClkrS+rwjHcoHb1nts5lP5bGkY0e4Z6Dfn5AoeQ4pEA1TOzANvPDqDZUBh+L4hUDkDSWg7sAH3pHK0BqZHwiDNrvE9ac8MFHJmrPVEr4dqhRwAip+YMAdGCwp0ofdm2amUL7aHTaCQhjgsAW306C8ksMwuFE+dAvsqJGZ1N5T2nxP9LYVWcc8ZsKV8VklZ/QS6ScBGz4Oi4YpPDvkt+ErY929t0vISjKengnDHhu/+WYaBxeVbre5G29hK+jrnmUHFBa6pjwCSFVJq8mRIn6KorGwltxObyFBxddf+kiAMDbvnqW3E6sZtW+mF+48hTD8ygjSE8D8h7IoyYYTh0nPY4DzkfMR2OXqpz+bh+dpjnh3UQktJ1HXh902A2ljmxUEMGWtcOc2fDTxEMheTqdvg2z7Ek/FIwq/curVbdHIrzeIXDis7LIRk4G8b4mA6nHG4KrzCF00LAV7Ph9Nx1wAgrxvBTq4bqnO2VP44vtDsQ/O+Dr+wZSlP3EL4PsAJznfa7YcvStvV1Nn6FrVY04W,iv:1vttyQqYffChK12Wy5KTZEZ00pESsMefXHbulKsDSqU=,tag:pzgw0jJqICaro2U2FfQEiA==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age16veg3fmvpfm7a89a9fc8dvvsxmsthlm70nfxqspr6t8vnf9wkcwsvdq38d
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBieVNmLy9zK1hXWHdGRHF3
WXd5b0pjR0pBaFkvVSt5ZVNyaUlCL0R1c2lnCkRCY1RmZnY1UVFuWURUMElHcFU1
WHgrZE5ZY3FQUVp2VkhIcHh3WDV4aUUKLS0tIGFRZFNsKzU4ZDhPQ0NLbmtPSEJB
YW8zNjhYWGNRcy9VbEZoaWplakVNWkUK48LBhFusDMZj2momMwRXdU7bLiGvzvqX
QwdxorLMP2/GW6x5xpFj1khLCwxYDOys4xGvmE89hYZa++OSYU+Ejw==
-----END AGE ENCRYPTED FILE-----
- recipient: age1ezq2j34qngky22enhnslx6hzh4ekwk8dtmn6c9us0uqxqpn7hgpsspjz58
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAwY0pGaUU3alJrYmhQM2Y4
TTMrbGsrRThlTUN1R3RCbFZFeVR1KzVDMWg4CnlQTzBzdTNPMTFvTUVYRXFEbUVP
YWFvRTJqZjcxZHhtRkJrajNuUWoyYzgKLS0tIEhVcFI4N0E4VEVOZDIxREJ4bkNi
UjRhRVpkTHF5a3p2bjhiVDZwMWRWMkEKpsHLWcPGQWpBo4Z8h7XFOP0bCct83BPj
d/QDjarzugd6jamWVXKidZwADxfP59Pvo9JmLlFL5isgZ1TL3ZHL+g==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2023-04-26T13:33:07Z"
mac: ENC[AES256_GCM,data:i/2+Qr0ihMaDPF22pyo7Qy3SYEr0Fr5dfRJSZ9xMu/5TFSm3GXNhd2KZVXjl6isbPbD22/siuI2MZeSB/iTFcRadtswmGnb6Lgsi8K/LULKvwXHf5t/Py/z59CpXBSgfUQQ6BuodNc25DRpCX8HEhFfd3Ajgyavc8vHVpnwG8eQ=,iv:Xu3WsyIqDbtReP9pBsiEf17pAbdVrY/y6wM2dleguFQ=,tag:gh6m7MUzMx2/lsZ5XExwyw==,type:str]
pgp: []
unencrypted_suffix: _unencrypted
version: 3.7.3

View File

@@ -0,0 +1,16 @@
{
services.home-assistant.config.shelly = {
FindAndroid = {
speech.text = "Send notification";
action = {
service = "notify.pushover";
data = {
message = "Phonefinderalert";
target = "android";
data.sound = "echo";
data.priority = 1;
};
};
};
};
}

View File

@@ -0,0 +1,59 @@
{
services.home-assistant.config = {
"automation wakeup" = {
alias = "wakeup";
hide_entity = true;
trigger = {
platform = "template";
value_template = ''
{{ now().timestamp() | timestamp_custom('%H:%M') == (as_timestamp(strptime(states('sensor.bedtime_alarm'), "%H:%M")) - 1800) | timestamp_custom('%H:%M', false) }}
'';
};
action = {
service_template = "switch.turn_on";
data_template = {
entity_id = "switch.coffee_switch";
};
};
};
"automation sleep" = {
alias = "sleep";
hide_entity = true;
trigger = [
{
platform = "event";
event_type = "shelly.click";
event_data = {
device = "shellybutton1-E8DB84AA196D";
};
}
{
platform = "event";
event_type = "shelly.click";
event_data = {
device = "shellybutton1-E8DB84AA136D";
};
}
];
action = [
{
choose = [
{
conditions = [ "{{ trigger.event.data.click_type == \"long\" }}" ];
sequence = [
{
service = "light.turn_off";
entity_id = "all";
}
{
service = "light.turn_on";
entity_id = "light.bedroom_bed";
}
];
}
];
}
];
};
};
}

View File

@@ -0,0 +1,66 @@
{
services.home-assistant = {
extraComponents = [ "snapcast" ];
config = {
# "media_player" = {
# platform = "snapcast";
# host = "snapcast.cloonar.com";
# };
"automation toilett_music" = {
alias = "toilett music";
hide_entity = true;
trigger = {
platform = "state";
entity_id = "light.toilett_switch";
};
action = {
choose = [
{
conditions = [ "{{trigger.to_state.state == 'on'}}" ];
sequence = [
{
service = "media_player.volume_mute";
target = {
entity_id = "media_player.snapcast_client_e4_5f_01_3c_fb_c3";
};
data = {
is_volume_muted = false;
};
}
];
}
{
conditions = [ "{{trigger.to_state.state == 'off'}}" ];
sequence = [
{
service = "media_player.volume_mute";
target = {
entity_id = "media_player.snapcast_client_e4_5f_01_3c_fb_c3";
};
data = {
is_volume_muted = true;
};
}
];
}
];
};
};
"automation piano" = {
alias = "piano";
hide_entity = true;
trigger = {
platform = "state";
entity_id = "media_player.snapcast_client_e4_5f_01_96_c1_1e";
attribute = "is_volume_muted";
};
action = {
service = "switch.turn_on";
target = {
entity_id = "switch.piano_switch_power";
};
};
};
};
};
}

90
utils/modules/howdy.nix Normal file
View File

@@ -0,0 +1,90 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.howdy;
ircfg = config.services.ir-toggle;
# `dark_threshold` is required for X1 Carbon 7th to work
configINI = pkgs.runCommand "config.ini" { } ''
cat ${cfg.package}/lib/security/howdy/config.ini > $out
substituteInPlace $out --replace 'device_path = none' 'device_path = ${cfg.device}'
substituteInPlace $out --replace 'dark_threshold = 50' 'dark_threshold = ${
toString cfg.dark-threshold
}'
substituteInPlace $out --replace 'certainty = 3.5' 'certainty = ${
toString cfg.certainty
}'
'';
pam-rule = pkgs.lib.mkDefault (pkgs.lib.mkBefore
"auth sufficient ${pkgs.pam_python}/lib/security/pam_python.so ${config.services.howdy.package}/lib/security/howdy/pam.py");
in {
options = {
services.ir-toggle = {
enable = mkOption {
type = types.bool;
default = false;
description = ''
Whether to enable Chicony IR Emitter toggler.
'';
};
};
services.howdy = {
enable = mkOption {
type = types.bool;
default = false;
description = ''
Whether to enable howdy and PAM module for face recognition.
'';
};
package = mkOption {
type = types.package;
default = pkgs.howdy;
defaultText = "pkgs.howdy";
description = ''
Howdy package to use.
'';
};
device = mkOption {
type = types.path;
default = "/dev/video0";
description = ''
Device file connected to the IR sensor.
'';
};
certainty = mkOption {
type = types.int;
default = 3.5;
description = ''
The certainty of the detected face belonging to the user of the account. On a scale from 1 to 10, values above 5 are not recommended.
'';
};
dark-threshold = mkOption {
type = types.int;
default = 50;
description = ''
Because of flashing IR emitters, some frames can be completely unlit. Skip the frame if the lowest 1/8 of the histogram is above this percentage of the total. The lower this setting is, the more dark frames are ignored.
'';
};
};
};
config = mkIf cfg.enable {
environment.systemPackages = [ cfg.package pkgs.ir_toggle ];
environment.etc."howdy/config.ini".source = configINI;
security.pam.services = {
sudo.text = pam-rule; # Sudo
login.text = pam-rule; # User login
polkit-1.text = pam-rule; # PolKit
i3lock.text = pam-rule; # i3lock
};
powerManagement.resumeCommands =
"${pkgs.ir_toggle}/bin/chicony-ir-toggle on";
services.udev.packages = [ pkgs.ir_toggle ];
};
}

View File

@@ -0,0 +1,45 @@
{
core = {
detection_notice = false;
timeout_notice = true;
no_confirmation = false;
suppress_unknown = false;
abort_if_ssh = true;
abort_if_lid_closed = true;
disabled = false;
use_cnn = false;
workaround = "off";
};
video = {
certainty = 3.5;
timeout = 4;
device_path = "/dev/video2";
warn_no_device = true;
max_height = 320;
frame_width = -1;
frame_height = -1;
dark_threshold = 60;
recording_plugin = "opencv";
device_format = "v4l2";
force_mjpeg = false;
exposure = -1;
rotate = 0;
};
snapshots = {
save_failed = false;
save_successful = false;
};
rubberstamps = {
enabled = false;
stamp_rules = "nod 5s failsafe min_distance=12";
};
debug = {
end_report = false;
verbose_stamps = false;
gtk_stdout = false;
};
}

View File

@@ -0,0 +1,123 @@
{ config, lib, pkgs, ... }:
with lib;
let
pam-rule = pkgs.lib.mkDefault (pkgs.lib.mkBefore
''
auth sufficient pam_unix.so try_first_pass nullok
auth sufficient ${config.services.howdy.package}/lib/security/pam_howdy.so
'');
pam-sudo-rule = pkgs.lib.mkDefault (pkgs.lib.mkBefore
''
auth sufficient ${config.services.howdy.package}/lib/security/pam_howdy.so
'');
cfg = config.services.howdy;
irCfg = config.services.linux-enable-ir-emitter;
settingsType = pkgs.formats.ini { };
in {
options = {
services.howdy = {
enable = mkOption {
type = types.bool;
default = false;
description = ''
Whether to enable howdy and PAM module for face recognition.
'';
};
package = mkOption {
type = types.package;
default = pkgs.howdy;
defaultText = "pkgs.howdy";
description = ''
Howdy package to use.
'';
};
settings = mkOption {
inherit (settingsType) type;
default = import ./config.nix;
description = mdDoc ''
Howdy configuration file. Refer to
<https://github.com/boltgolt/howdy/blob/beta/howdy/src/config.ini>
for options.
'';
};
};
services.linux-enable-ir-emitter = {
enable = mkEnableOption (mdDoc "") // {
description = mdDoc ''
Whether to enable IR emitter hardware. Designed to be used with the
Howdy facial authentication. After enabling the service, configure
the emitter with `sudo linux-enable-ir-emitter configure`.
'';
};
package = mkPackageOptionMD pkgs "linux-enable-ir-emitter" {} // {
description = mdDoc ''
Package to use for the Linux Enable IR Emitter service.
'';
};
device = mkOption {
type = types.str;
default = "video2";
description = mdDoc ''
IR camera device to depend on. For example, for `/dev/video2`
the value would be `video2`. Find this with the command
{command}`realpath /dev/v4l/by-path/<generated-driver-name>`.
'';
};
};
};
config = mkIf cfg.enable {
# environment.systemPackages = [ cfg.package pkgs.ir_toggle ];
# environment.etc."howdy/config.ini".source = configINI;
# security.pam.services = {
# sudo.text = pam-rule; # Sudo
# login.text = pam-rule; # User login
# polkit-1.text = pam-rule; # PolKit
# i3lock.text = pam-rule; # i3lock
# };
# powerManagement.resumeCommands =
# "${pkgs.ir_toggle}/bin/chicony-ir-toggle on";
# services.udev.packages = [ pkgs.ir_toggle ];
environment.systemPackages = [ cfg.package irCfg.package ];
security.pam.services = {
sudo.text = pam-sudo-rule; # Sudo
login.text = pam-rule; # User login
polkit-1.text = pam-rule; # PolKit
swaylock.text = pam-rule; # i3lock
# gdm-password.text = pam-rule; # i3lock
};
systemd.services.linux-enable-ir-emitter = rec {
description = "Enable the infrared emitter";
script = "${getExe irCfg.package} run";
wantedBy = [
"multi-user.target"
"suspend.target"
"hybrid-sleep.target"
"hibernate.target"
"suspend-then-hibernate.target"
];
after = wantedBy ++ [ "dev-${irCfg.device}.device" ];
};
systemd.tmpfiles.rules = [
"d /var/lib/linux-enable-ir-emitter 0755 root root - -"
];
environment.etc."linux-enable-ir-emitter".source = "/var/lib/linux-enable-ir-emitter";
environment.etc."howdy/config.ini".source = settingsType.generate "howdy-config.ini" cfg.settings;
};
}

22
utils/modules/i3.nix Normal file
View File

@@ -0,0 +1,22 @@
{ config, pkgs, callPackage, ... }:
{
environment.pathsToLink = [ "/libexec" ]; # links /libexec from derivations to /run/current-system/sw
services.xserver = {
enable = true;
desktopManager = {
xterm.enable = false;
};
windowManager.i3 = {
enable = true;
extraPackages = with pkgs; [
dmenu #application launcher most people use
i3status # gives you the default i3 status bar
i3lock #default i3 screen locker
i3blocks #if you are planning on using i3blocks over i3status
];
};
};
}

View File

@@ -0,0 +1,21 @@
{ config, ... }: {
services.influxdb = {
enable = true;
extraConfig = {
http = {
auth-enabled = true;
log-enabled = false;
https-enabled = true;
https-certificate = "/var/lib/acme/influxdb.cloonar.com/fullchain.pem";
https-private-key = "/var/lib/acme/influxdb.cloonar.com/key.pem";
};
};
};
networking.firewall.allowedTCPPorts = [ 8086 ];
security.acme.certs."influxdb.cloonar.com" = {
postRun = "systemctl restart influxdb.service";
group = "influxdb";
};
}

View File

@@ -0,0 +1,16 @@
{ config, ... }:
{
sops.secrets.lego-credentials = {
sopsFile = ./secrets.yaml;
};
security.acme.acceptTerms = true;
security.acme.defaults.email = "admin+acme@cloonar.com";
security.acme.defaults = {
dnsProvider = "hetzner";
credentialsFile = config.sops.secrets.lego-credentials.path;
# We don't need to wait for propagation since this is a local DNS server
dnsPropagationCheck = true;
};
}

View File

@@ -0,0 +1,84 @@
lego-credentials: ENC[AES256_GCM,data:cn7n1jOammEdvzYzBKJ086c1bHc77GN74uncg35ClaTBvb5w3F0lQazJqBJoIf365Q==,iv:FLrr7WwGgzjuENOEi/Sf8Ti6wcQLPnBkJ+/DxyCUM54=,tag:yQnDsDz+btx3MQu/4w2ixg==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age16veg3fmvpfm7a89a9fc8dvvsxmsthlm70nfxqspr6t8vnf9wkcwsvdq38d
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBocUx0b1VtSlF4SVpvMXpr
NjZSSHdkaDVoeDRCTC9LRFI1bkJRQTMyUFdJCjJvN2NyY1JLMkVtUTF2eGN3Lzh5
R3M4NUk2WUpFMTM4MHQxM2k0dkdxUWcKLS0tIFkrMUVSaHVCaEYydERacFBtQVVt
dXFENTFldVFWN3RQWTBKZHVtc0tza1kKeKGChclZahfDACUJxPsTn+4XomqifXP4
VH+BxqmwkhgryRDoRrVy+vQnyK95WaDo3S/UIR2zgUR+cezt1DzR2A==
-----END AGE ENCRYPTED FILE-----
- recipient: age106n5n3rrrss45eqqzz8pq90la3kqdtnw63uw0sfa2mahk5xpe30sxs5x58
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBnckpCQVZaOE9NT084d0Zk
TUtXN3EvcFZoOCs1aFloK2RSTVFyT2RWUzBVCjdCQzNGaWpqejhNdUtnZTl6RHpY
b2dvMjZIV2ZGYkwyNVpxaHRPUmt3bmsKLS0tIHJReVpvTzBqYS9PVThmRzZzZUtI
WjZmMXIxOWFScGlNSFdwbXdQcXB3d1UKHAkThsJ2unza8Yz/l0umryT8li74LKre
dQuP41RQOQBHisUUZhWeYkM+wJzayXr426IK19zAHPuNeutqcewYcA==
-----END AGE ENCRYPTED FILE-----
- recipient: age1y6lvl5jkwc47p5ae9yz9j9kuwhy7rtttua5xhygrgmr7ehd49svsszyt42
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBaSGhiSkFKbHpGcjljZ3Uw
ZWZ5MS82Wk9YRnZlQVk2V2laMzRkK3dBdWg4CjJ5Wkd0bnNXbVpMYVUxSVR1Nnpn
dkFnbTV4eTYwWmdzWU9PZlozNytBWk0KLS0tIGVTL1RFbzBBM25nbFVtOEVQMmVm
bmQvemhIeU8wTGswTEN2ZjA2RjdaTW8KlorFf+agQuSwbN3Fkr5bUC2Ca6Sz8hHy
Faq+uNlMWHCrvE1DBP34D41LxCLDaDMYIJyUG7A4MZE2WUrJZ9c0vQ==
-----END AGE ENCRYPTED FILE-----
- recipient: age1ezq2j34qngky22enhnslx6hzh4ekwk8dtmn6c9us0uqxqpn7hgpsspjz58
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBnT2VQd2VKeTFsQ3BPbXNq
cWRTREx0UE54RmNDSlBwK082azRZalQ0aUhVCk1HV0Y3RnFYbS83NXcvY3IwSGVG
ZElxcm1ETlFvVkhjR3RVNnNJQmR2dzAKLS0tIGpoYytWL25nQkFSMm5hQ29yYUd3
UEp1cndyMG9Ba0RnT3NRdHAzRzBjdDAKIHXX0rnPkEz6Smw3sH8RgDdS92yOoFxz
6uFUrqbxAW1+6EpgSPCi4GioAZyFayHdeuXQ5J9vApCDhHdsd6jMzw==
-----END AGE ENCRYPTED FILE-----
- recipient: age1jyeppc8yl2twnv8fwcewutd5gjewnxl59lmhev6ygds9qel8zf8syt7zz4
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBBRVEyK3M2aXdwV3RTMGJy
NlBUaWFKemJDRHB3ck5xVG1BWW1CUjc1OEdZCnUzSktiUkRmcTNwOWZXTFhnUCtD
bHFCZ1ZhKytGc1hoOVQ0SFFyUkpmOHMKLS0tIGNWV3Vrd2J2TTYrUUhaSW0yak5W
UTRGd0FaZUk1RVFqS3NXWHZ6SFQ4MTAKsIWMYxczPfDg7G/H5Rcm7sD/2zPXWJfl
c2PiNSeZAfuCqAU/a9/2rz0kk3LdAW7d+foBOPeMkWnKs2pFJxNMXw==
-----END AGE ENCRYPTED FILE-----
- recipient: age1azmxsw5llmp2nnsv3yc2l8paelmq9rfepxd8jvmswgsmax0qyyxqdnsc7t
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBTUTZKRGdzSHgybnB6ZExx
UVRCNTROS3QyOFBYSFc5blFEQythTndRSHhrCjJxcTNqejUxQWxRZzhhZVhNcjlR
MFY4LzdicGUwMm13R1k2ZUdDc0VrY2MKLS0tIFVyNGlJU3NyQnkzZEg4SEM1T1NZ
RHNUd053UUJyMnprbi9DR0JnSEQ5YjQKeXRdvnQRtkLs6yqVKlul4wp4PXQTpktZ
cUUWEaajUmXoEeHjFkfNqtsJkVG6ixnzs9tu/GeOCbTCZ9eFokUg2g==
-----END AGE ENCRYPTED FILE-----
- recipient: age1zkzpnfeakyvg3fqtyay32sushjx2hqe28y6hs6ss7plemzqjqa5s6s5yu3
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGNHJFa0ltM0JiQ1hOa0pQ
K3FndzhxaTBwZWgzNWg4RXBQdDV0WlZNZ1g4CkVhUlA2d0JjanorSlpyYVBUaEli
Mnl2VmJTNG9DcnZsSXZpUFZXTDZQRVUKLS0tIEtDZ2J3L0RtV1BybEJDZ0k2bGZV
YWY5QjlZZ1J2OEw2U0luZHNWQVFmRjQKZ9A54c5AXSm2aNasBinaWPDIo/xDXFqZ
7+ZTJ82QiWBXpaLIpmPim3e9JHVzZ8NKdN0Y7imsYdR2gXRsxyv1SQ==
-----END AGE ENCRYPTED FILE-----
- recipient: age14uarclad0ty5supc8ep09793xrnwkv8a4h9j0fq8d8lc92n2dadqkf64vw
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA1K1RSTVZOYmFxalFxc1g1
OHpaUXNLOHF2WnQ1VUxLUENwclJoQXl2b20wCnJnOUs0cXlMTDRXdktJZ2x6bjNJ
UWJjYkZwR2ZKNnpsaVN0bzBWODZNL3MKLS0tIEUraytIc1d6dVVqa0VaSWJpcWRn
UWswVG5PaTdDZHlybGxpZ2tKb1liOWsKOuMm2+kofwGqC95KhfEecjwzjNCHPRRk
/61zp39+U6PeqP0gTbcy959aSDhfucrZKhBKP2VsTgP0BLDfZR2K4Q==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2022-11-09T07:12:13Z"
mac: ENC[AES256_GCM,data:gqsD5gTtE5ZqWzWKAAIscecvIsGSC9j4Cnbik6Yk7Jf7Z5/NIxbkInzDsLmlU3ObbLZAhGAlOAKIrUVy37rCcEZ+I04ICXK1dmUdsVud6E4SvTdDjh9qlXTbEkcDCY2YqXlTuQl6IZyveaPuF6fRe1FMh8JEpDv/foZTl8+AuQQ=,iv:+nV6YW9m1B0qo7xbB1lw9dgiQ877GQ6OxMqjk7lei10=,tag:NmeSwBWRKpqlwZxYYC7trg==,type:str]
pgp: []
unencrypted_suffix: _unencrypted
version: 3.7.3

59
utils/modules/mopidy.nix Normal file
View File

@@ -0,0 +1,59 @@
{ pkgs, lib, ... }:
let
mopidy-autoplay = pkgs.python3Packages.buildPythonApplication rec {
pname = "Mopidy-Autoplay";
version = "0.2.3";
src = pkgs.python3Packages.fetchPypi {
inherit pname version;
sha256 = "sha256-E2Q+Cn2LWSbfoT/gFzUfChwl67Mv17uKmX2woFz/3YM=";
};
propagatedBuildInputs = [
pkgs.mopidy
] ++ (with pkgs.python3Packages; [
configobj
]);
# no tests implemented
doCheck = false;
meta = with lib; {
homepage = "https://codeberg.org/sph/mopidy-autoplay";
};
};
in
{
services.mopidy = {
enable = true;
extensionPackages = [ pkgs.mopidy-iris pkgs.mopidy-tunein mopidy-autoplay ];
configuration = ''
[audio]
output = audioresample ! audioconvert ! audio/x-raw,rate=48000,channels=2,format=S16LE ! wavenc ! filesink location=/run/snapserver/mopidy
[file]
enabled = false
[autoplay]
enabled = true
'';
};
services.nginx.virtualHosts."mopidy.cloonar.com" = {
forceSSL = true;
enableACME = true;
acmeRoot = null;
extraConfig = ''
proxy_buffering off;
'';
locations."/".extraConfig = ''
proxy_pass http://127.0.0.1:6680;
proxy_set_header Host $host;
proxy_redirect http:// https://;
proxy_http_version 1.1;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
'';
};
}

View File

@@ -0,0 +1,28 @@
{ config, pkgs, ... }:
{
services.mosquitto = {
enable = true;
listeners = [
{
users."espresense" = {
password = "insecure-password";
acl = [ "readwrite #" ];
};
users."home-assistant" = {
hashedPassword = "$7$101$7uaagoQWQ3ICJ/wg$5cWZs4ae4DjToe44bOzpDopPv1kRaaVD+zF6BE64yDJH2/MBqXfD6f2/o9M/65ArhV92DAK+txXRYsEcZLl45A==";
acl = [ "readwrite #" ];
};
users."ps5-mqtt" = {
password = "insecure-password";
acl = [ "readwrite #" ];
};
}
];
};
networking.firewall = {
allowedTCPPorts = [ 1883 ];
};
}

78
utils/modules/mysql.nix Normal file
View File

@@ -0,0 +1,78 @@
{ pkgs, ... }:
let
mysqlCreateDatabase = pkgs.writeShellScriptBin "mysql-create-database" ''
#!/usr/bin/env bash
if [ $# -lt 2 ]
then
echo "Usage: $0 <database> <host>"
exit 1
fi
if ! [ $EUID -eq 0 ]
then
echo "Must be root!" >&2
exit 1
fi
DB="$1"
HOST="$2"
PASSWORD="$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 64 | xargs)"
cat <<EOF | mysql --host localhost --user root
create database $DB;
grant usage on $DB.* to '$DB'@'$HOST' identified by '$PASSWORD';
grant all privileges on $DB.* to '$DB'@'$HOST';
EOF
echo
echo "Password for user $DB is:"
echo
echo $PASSWORD
echo
'';
mysqlDeleteDatabase = pkgs.writeShellScriptBin "mysql-delete-database" ''
#!/usr/bin/env bash
if [ $# -lt 1 ]
then
echo "Usage: $0 <database>"
exit 1
fi
if ! [ $EUID -eq 0 ]
then
echo "Must be root!" >&2
exit 1
fi
DB="$1"
PASSWORD="$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 64 | xargs)"
cat <<EOF | mysql --host localhost --user root
drop database $DB;
drop user '$DB';
EOF
echo
echo "Dropped database $DB!"
echo
'';
in {
environment.systemPackages = [
mysqlCreateDatabase
mysqlDeleteDatabase
];
services.mysql = {
enable = true;
package = pkgs.mariadb;
settings = {
mysqld = {
max_allowed_packet = "64M";
};
};
};
services.mysqlBackup.enable = true;
services.mysqlBackup.databases = [ "mysql" ];
}

View File

@@ -0,0 +1,13 @@
{ pkgs, ... }:
let
mysql-scripts = pkgs.callPackage ./pkgs/mysql-scripts.nix {};
in {
environment.systemPackages = [
mysql-scripts
];
services.mysql = {
enable = true;
package = pkgs.mariadb;
};
}

View File

@@ -0,0 +1,14 @@
{
stdenv,
lib,
bash,
}:
stdenv.mkDerivation {
name = "mysql-scripts";
src = ./scripts;
buildInputs = [ bash ];
#nativeBuildInputs = [lib.makeWrapper];
installPhase = ''
install -D --target $out/bin *
'';
}

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env bash
if [ $# -lt 1 ]
then
echo "Usage: $0 <database>"
exit 1
fi
if ! [ $EUID -eq 0 ]
then
echo "Must be root!" >&2
exit 1
fi
DB="$1"
PASSWORD="$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 64 | xargs)"
cat <<EOF | mysql --host localhost --user root
create database $DB;
grant usage on $DB.* to '$DB'@'%' identified by '$PASSWORD';
grant all privileges on $DB.* to '$DB'@'%';
EOF
echo
echo "Password for user $DB is:"
echo
echo $PASSWORD
echo

View File

@@ -0,0 +1,25 @@
#!/usr/bin/env bash
if [ $# -lt 1 ]
then
echo "Usage: $0 <database>"
exit 1
fi
if ! [ $EUID -eq 0 ]
then
echo "Must be root!" >&2
exit 1
fi
DB="$1"
PASSWORD="$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 64 | xargs)"
cat <<EOF | mysql --host localhost --user root
drop database $DB;
drop user '$DB';
EOF
echo
echo "Dropped database $DB!"
echo

30
utils/modules/netdata.nix Normal file
View File

@@ -0,0 +1,30 @@
{ config, pkgs, ... }:
{
environment.systemPackages = with pkgs; [
python39
];
services.netdata.configDir."python.d.conf" = pkgs.writeText "python.d.conf" ''
postfix: yes
'';
services.netdata = {
enable = true;
python.enable = true;
config = {
global = {
# uncomment to reduce memory to 32 MB
"page cache size" = 32;
# update interval
"update every" = 15;
};
ml = {
# enable machine learning
"enabled" = "yes";
};
};
};
}

32
utils/modules/nginx.nix Normal file
View File

@@ -0,0 +1,32 @@
{ config, pkgs, ... }:
{
environment.systemPackages = with pkgs; [
imagemagick
ghostscript
];
systemd.services.nginx.serviceConfig.ProtectHome = "read-only";
systemd.services.nginx_setup = {
wantedBy = [ "multi-user.target" ];
before = [ "nginx.service" ];
script = ''
mkdir -p /var/www
chown nginx:nginx /var/www
chmod 755 /var/www
'';
serviceConfig.Type = "oneshot";
};
services.nginx = {
enable = true;
recommendedGzipSettings = true;
recommendedOptimisation = true;
recommendedProxySettings = true;
recommendedTlsSettings = true;
sslCiphers = "AES256+EECDH:AES256+EDH:!aNULL";
};
}

View File

@@ -0,0 +1,135 @@
{ pkgs, lib, config, ... }:
let
domain = "cloonar.dev";
dataDir = "/var/www/${domain}";
in {
systemd.services."phpfpm-${domain}".serviceConfig.ProtectHome = lib.mkForce false;
services.phpfpm.pools."${domain}" = {
user = domain;
settings = {
"listen.owner" = config.services.nginx.user;
"pm" = "dynamic";
"pm.max_children" = 32;
"pm.max_requests" = 500;
"pm.start_servers" = 2;
"pm.min_spare_servers" = 2;
"pm.max_spare_servers" = 5;
"php_admin_value[error_log]" = "stderr";
"php_admin_flag[log_errors]" = true;
"catch_workers_output" = true;
"access.log" = "/var/log/$pool.access.log";
};
phpPackage = pkgs.php81;
phpEnv."PATH" = lib.makeBinPath [ pkgs.php81 ];
};
services.nginx.virtualHosts."cloonar.dev" = {
forceSSL = true;
enableACME = true;
acmeRoot = null;
root = "${dataDir}";
locations."/favicon.ico".extraConfig = ''
log_not_found off;
access_log off;
'';
# TYPO3 - Rule for versioned static files, configured through:
# - $GLOBALS['TYPO3_CONF_VARS']['BE']['versionNumberInFilename']
# - $GLOBALS['TYPO3_CONF_VARS']['FE']['versionNumberInFilename']
extraConfig = ''
if (!-e $request_filename) {
rewrite ^/(.+)\.(\d+)\.(php|js|css|png|jpg|gif|gzip)$ /$1.$3 last;
}
'';
# TYPO3 - Block access to composer files
locations."~* composer\.(?:json|lock)".extraConfig = ''
deny all;
'';
# TYPO3 - Block access to flexform files
locations."~* flexform[^.]*\.xml".extraConfig = ''
deny all;
'';
# TYPO3 - Block access to language files
locations."~* locallang[^.]*\.(?:xml|xlf)$".extraConfig = ''
deny all;
'';
# TYPO3 - Block access to static typoscript files
locations."~* ext_conf_template\.txt|ext_typoscript_constants\.txt|ext_typoscript_setup\.txt".extraConfig = ''
deny all;
'';
# TYPO3 - Block access to miscellaneous protected files
locations."~* /.*\.(?:bak|co?nf|cfg|ya?ml|ts|typoscript|tsconfig|dist|fla|in[ci]|log|sh|sql|sqlite)$".extraConfig = ''
deny all;
'';
# TYPO3 - Block access to recycler and temporary directories
locations."~ _(?:recycler|temp)_/".extraConfig = ''
deny all;
'';
# TYPO3 - Block access to configuration files stored in fileadmin
locations."~ fileadmin/(?:templates)/.*\.(?:txt|ts|typoscript)$".extraConfig = ''
deny all;
'';
# TYPO3 - Block access to libraries, source and temporary compiled data
locations."~ ^(?:vendor|typo3_src|typo3temp/var)".extraConfig = ''
deny all;
'';
# TYPO3 - Block access to protected extension directories
locations."~ (?:typo3conf/ext|typo3/sysext|typo3/ext)/[^/]+/(?:Configuration|Resources/Private|Tests?|Documentation|docs?)/".extraConfig = ''
deny all;
'';
locations."/".extraConfig = ''
index index.php index.html;
try_files $uri $uri/ /index.php$is_args$args;
'';
# TYPO3 Backend URLs
locations."/typo3".extraConfig = ''
rewrite ^ /typo3/;
'';
locations."/typo3/".extraConfig = ''
try_files $uri /typo3/index.php$is_args$args;
'';
locations."~ [^/]\.php(/|$)".extraConfig = ''
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
if (!-f $document_root$fastcgi_script_name) {
return 404;
}
include ${pkgs.nginx}/conf/fastcgi_params;
include ${pkgs.nginx}/conf/fastcgi.conf;
fastcgi_buffer_size 32k;
fastcgi_buffers 8 16k;
fastcgi_connect_timeout 240s;
fastcgi_read_timeout 240s;
fastcgi_send_timeout 240s;
fastcgi_pass unix:${config.services.phpfpm.pools."${domain}".socket};
fastcgi_index index.php;
'';
};
users.users."${domain}" = {
isSystemUser = true;
createHome = true;
home = dataDir;
homeMode= "770";
#home = "/home/${domain}";
group = "nginx";
};
users.groups.${domain} = {};
}

View File

@@ -0,0 +1,55 @@
{ config, pkgs, ... }:
{
imports = [
./cloonar.dev.nix
];
environment.systemPackages = with pkgs; [
imagemagick
ghostscript
];
systemd.services.nginx.serviceConfig.ProtectHome = "read-only";
services.nginx = {
enable = true;
recommendedGzipSettings = true;
recommendedOptimisation = true;
recommendedProxySettings = true;
recommendedTlsSettings = true;
sslCiphers = "AES256+EECDH:AES256+EDH:!aNULL";
commonHttpConfig = ''
# Add HSTS header with preloading to HTTPS requests.
# Adding this header to HTTP requests is discouraged
map $scheme $hsts_header {
https "max-age=31536000; includeSubdomains; preload";
}
add_header Strict-Transport-Security $hsts_header;
# Enable CSP for your services.
#add_header Content-Security-Policy "script-src 'self'; object-src 'none'; base-uri 'none';" always;
# Minimize information leaked to other domains
add_header 'Referrer-Policy' 'origin-when-cross-origin';
# Disable embedding as a frame
add_header X-Frame-Options DENY;
# Prevent injection of code in other mime types (XSS Attacks)
add_header X-Content-Type-Options nosniff;
# Enable XSS protection of the browser.
# May be unnecessary when CSP is configured properly (see above)
add_header X-XSS-Protection "1; mode=block";
# This might create errors
proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict";
'';
};
}

View File

@@ -0,0 +1,48 @@
{ config, ... }:
{
imports = [
./cloonar.dev.nix
];
services.nginx = {
enable = true;
recommendedGzipSettings = true;
recommendedOptimisation = true;
recommendedProxySettings = true;
recommendedTlsSettings = true;
sslCiphers = "AES256+EECDH:AES256+EDH:!aNULL";
commonHttpConfig = ''
# Add HSTS header with preloading to HTTPS requests.
# Adding this header to HTTP requests is discouraged
map $scheme $hsts_header {
https "max-age=31536000; includeSubdomains; preload";
}
add_header Strict-Transport-Security $hsts_header;
# Enable CSP for your services.
#add_header Content-Security-Policy "script-src 'self'; object-src 'none'; base-uri 'none';" always;
# Minimize information leaked to other domains
add_header 'Referrer-Policy' 'origin-when-cross-origin';
# Disable embedding as a frame
add_header X-Frame-Options DENY;
# Prevent injection of code in other mime types (XSS Attacks)
add_header X-Content-Type-Options nosniff;
# Enable XSS protection of the browser.
# May be unnecessary when CSP is configured properly (see above)
add_header X-XSS-Protection "1; mode=block";
# This might create errors
proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict";
'';
};
}

7
utils/modules/nur.nix Normal file
View File

@@ -0,0 +1,7 @@
{
nixpkgs.config.packageOverrides = pkgs: {
nur = import (builtins.fetchTarball "https://github.com/nix-community/NUR/archive/master.tar.gz") {
inherit pkgs;
};
};
}

View File

@@ -0,0 +1,221 @@
local function is_ft(b, ft)
return vim.bo[b].filetype == ft
end
local function diagnostics_indicator(num, _, diagnostics, _)
local result = {}
local symbols = {
error = Icons.diagnostics.Error,
warning = Icons.diagnostics.Warning,
info = Icons.diagnostics.Information,
}
for name, count in pairs(diagnostics) do
if symbols[name] and count > 0 then
table.insert(result, symbols[name] .. " " .. count)
end
end
result = table.concat(result, " ")
return #result > 0 and result or ""
end
local function custom_filter(buf, buf_nums)
local logs = vim.tbl_filter(function(b)
return is_ft(b, "log")
end, buf_nums)
if vim.tbl_isempty(logs) then
return true
end
local tab_num = vim.fn.tabpagenr()
local last_tab = vim.fn.tabpagenr "$"
local is_log = is_ft(buf, "log")
if last_tab == 1 then
return true
end
-- only show log buffers in secondary tabs
return (tab_num == last_tab and is_log) or (tab_num ~= last_tab and not is_log)
end
local config = {
active = true,
on_config_done = nil,
keymap = {
normal_mode = {},
},
highlights = {
background = {
italic = true,
},
buffer_selected = {
bold = true,
},
},
options = {
mode = "buffers", -- set to "tabs" to only show tabpages instead
numbers = "none", -- can be "none" | "ordinal" | "buffer_id" | "both" | function
close_command = function(bufnr) -- can be a string | function, see "Mouse actions"
buf_kill("bd", bufnr, false)
end,
right_mouse_command = "vert sbuffer %d", -- can be a string | function, see "Mouse actions"
left_mouse_command = "buffer %d", -- can be a string | function, see "Mouse actions"
middle_mouse_command = nil, -- can be a string | function, see "Mouse actions"
indicator = {
icon = Icons.ui.BoldLineLeft, -- this should be omitted if indicator style is not 'icon'
style = "icon", -- can also be 'underline'|'none',
},
buffer_close_icon = Icons.ui.Close,
modified_icon = Icons.ui.Circle,
close_icon = Icons.ui.BoldClose,
left_trunc_marker = Icons.ui.ArrowCircleLeft,
right_trunc_marker = Icons.ui.ArrowCircleRight,
--- name_formatter can be used to change the buffer's label in the bufferline.
--- Please note some names can/will break the
--- bufferline so use this at your discretion knowing that it has
--- some limitations that will *NOT* be fixed.
name_formatter = function(buf) -- buf contains a "name", "path" and "bufnr"
-- remove extension from markdown files for example
if buf.name:match "%.md" then
return vim.fn.fnamemodify(buf.name, ":t:r")
end
end,
max_name_length = 18,
max_prefix_length = 15, -- prefix used when a buffer is de-duplicated
truncate_names = true, -- whether or not tab names should be truncated
tab_size = 18,
diagnostics = "nvim_lsp",
diagnostics_update_in_insert = false,
diagnostics_indicator = diagnostics_indicator,
-- NOTE: this will be called a lot so don't do any heavy processing here
custom_filter = custom_filter,
offsets = {
{
filetype = "undotree",
text = "Undotree",
highlight = "PanelHeading",
padding = 1,
},
{
filetype = "NvimTree",
text = "Explorer",
highlight = "PanelHeading",
padding = 1,
},
{
filetype = "DiffviewFiles",
text = "Diff View",
highlight = "PanelHeading",
padding = 1,
},
{
filetype = "flutterToolsOutline",
text = "Flutter Outline",
highlight = "PanelHeading",
},
{
filetype = "packer",
text = "Packer",
highlight = "PanelHeading",
padding = 1,
},
},
color_icons = true, -- whether or not to add the filetype icon highlights
show_buffer_icons = true, -- disable filetype icons for buffers
show_buffer_close_icons = true,
show_close_icon = false,
show_tab_indicators = true,
persist_buffer_sort = true, -- whether or not custom sorted buffers should persist
-- can also be a table containing 2 custom separators
-- [focused and unfocused]. eg: { '|', '|' }
separator_style = "thin",
enforce_regular_tabs = false,
always_show_bufferline = false,
hover = {
enabled = false, -- requires nvim 0.8+
delay = 200,
reveal = { "close" },
},
sort_by = "id",
},
}
--require('keymappings').load(config.keymap)
require("bufferline").setup({
options = config.options,
highlights = config.highlights,
})
--stylua: ignore
-- Common kill function for bdelete and bwipeout
-- credits: based on bbye and nvim-bufdel
---@param kill_command? string defaults to "bd"
---@param bufnr? number defaults to the current buffer
---@param force? boolean defaults to false
function buf_kill(kill_command, bufnr, force)
kill_command = kill_command or "bd"
local bo = vim.bo
local api = vim.api
local fmt = string.format
local fnamemodify = vim.fn.fnamemodify
if bufnr == 0 or bufnr == nil then
bufnr = api.nvim_get_current_buf()
end
local bufname = api.nvim_buf_get_name(bufnr)
if not force then
local warning
if bo[bufnr].modified then
warning = fmt([[No write since last change for (%s)]], fnamemodify(bufname, ":t"))
elseif api.nvim_buf_get_option(bufnr, "buftype") == "terminal" then
warning = fmt([[Terminal %s will be killed]], bufname)
end
if warning then
vim.ui.input({
prompt = string.format([[%s. Close it anyway? [y]es or [n]o (default: no): ]], warning),
}, function(choice)
if choice:match "ye?s?" then force = true end
end)
if not force then return end
end
end
-- Get list of windows IDs with the buffer to close
local windows = vim.tbl_filter(function(win)
return api.nvim_win_get_buf(win) == bufnr
end, api.nvim_list_wins())
if force then
kill_command = kill_command .. "!"
end
-- Get list of active buffers
local buffers = vim.tbl_filter(function(buf)
return api.nvim_buf_is_valid(buf) and bo[buf].buflisted
end, api.nvim_list_bufs())
-- If there is only one buffer (which has to be the current one), vim will
-- create a new buffer on :bd.
-- For more than one buffer, pick the previous buffer (wrapping around if necessary)
if #buffers > 1 and #windows > 0 then
for i, v in ipairs(buffers) do
if v == bufnr then
local prev_buf_idx = i == 1 and (#buffers - 1) or (i - 1)
local prev_buffer = buffers[prev_buf_idx]
for _, win in ipairs(windows) do
api.nvim_win_set_buf(win, prev_buffer)
end
end
end
end
-- Check if buffer still exists, to ensure the target buffer wasn't killed
-- due to options like bufhidden=wipe.
if api.nvim_buf_is_valid(bufnr) and bo[bufnr].buflisted then
vim.cmd(string.format("%s %d", kill_command, bufnr))
end
end

View File

@@ -0,0 +1,155 @@
Icons = {
kind = {
Array = "",
Boolean = "",
Class = "",
Color = "",
Constant = "",
Constructor = "",
Enum = "",
EnumMember = "",
Event = "",
Field = "",
File = "",
Folder = "",
Function = "",
Interface = "",
Key = "",
Keyword = "",
Method = "",
Module = "",
Namespace = "",
Null = "",
Number = "",
Object = "",
Operator = "",
Package = "",
Property = "",
Reference = "",
Snippet = "",
String = "",
Struct = "",
Text = "",
TypeParameter = "",
Unit = "",
Value = "",
Variable = "",
},
git = {
LineAdded = "",
LineModified = "",
LineRemoved = "",
FileDeleted = "",
FileIgnored = "",
FileRenamed = "",
FileStaged = "S",
FileUnmerged = "",
FileUnstaged = "",
FileUntracked = "U",
Diff = "",
Repo = "",
Octoface = "",
Branch = "",
},
ui = {
ArrowCircleDown = "",
ArrowCircleLeft = "",
ArrowCircleRight = "",
ArrowCircleUp = "",
BoldArrowDown = "",
BoldArrowLeft = "",
BoldArrowRight = "",
BoldArrowUp = "",
BoldClose = "",
BoldDividerLeft = "",
BoldDividerRight = "",
BoldLineLeft = "",
BookMark = "",
BoxChecked = "",
Bug = "",
Stacks = "",
Scopes = "",
Watches = "",
DebugConsole = "",
Calendar = "",
Check = "",
ChevronRight = ">",
ChevronShortDown = "",
ChevronShortLeft = "",
ChevronShortRight = "",
ChevronShortUp = "",
Circle = "",
Close = "",
CloudDownload = "",
Code = "",
Comment = "",
Dashboard = "",
DividerLeft = "",
DividerRight = "",
DoubleChevronRight = "»",
Ellipsis = "",
EmptyFolder = "",
EmptyFolderOpen = "",
File = "",
FileSymlink = "",
Files = "",
FindFile = "",
FindText = "",
Fire = "",
Folder = "",
FolderOpen = "",
FolderSymlink = "",
Forward = "",
Gear = "",
History = "",
Lightbulb = "",
LineLeft = "",
LineMiddle = "",
List = "",
Lock = "",
NewFile = "",
Note = "",
Package = "",
Pencil = "",
Plus = "",
Project = "",
Search = "",
SignIn = "",
SignOut = "",
Tab = "",
Table = "",
Target = "",
Telescope = "",
Text = "",
Tree = "",
Triangle = "",
TriangleShortArrowDown = "",
TriangleShortArrowLeft = "",
TriangleShortArrowRight = "",
TriangleShortArrowUp = "",
},
diagnostics = {
BoldError = "",
Error = "",
BoldWarning = "",
Warning = "",
BoldInformation = "",
Information = "",
BoldQuestion = "",
Question = "",
BoldHint = "",
Hint = "",
Debug = "",
Trace = "",
},
misc = {
Robot = "",
Squirrel = "",
Tag = "",
Watch = "",
Smiley = "",
Package = "",
CircuitBoard = "",
},
}

View File

@@ -0,0 +1,58 @@
-- vim.opt.expandtab = true
-- vim.opt.hidden = true
-- vim.opt.incsearch = true
-- vim.opt.mouse = "a"
-- vim.opt.number = true
-- vim.opt.shiftwidth = 2
-- vim.opt.splitbelow = true
-- vim.opt.splitright = true
-- vim.opt.signcolumn = "yes:3"
-- vim.opt.tabstop = 2
-- vim.opt.timeoutlen = 0
-- vim.wo.wrap = false
-- vim.opt.exrc = true
-- vim.cmd("syntax on")
vim.opt.backup = false -- creates a backup file
vim.opt.clipboard = "unnamedplus" -- allows neovim to access the system clipboard
vim.opt.cmdheight = 2 -- more space in the neovim command line for displaying messages
vim.opt.colorcolumn = "99999" -- fixes indentline for now
vim.opt.completeopt = { "menuone", "noselect" }
vim.opt.conceallevel = 0 -- so that `` is visible in markdown files
vim.opt.fileencoding = "utf-8" -- the encoding written to a file
vim.opt.foldmethod = "manual" -- folding set to "expr" for treesitter based folding
vim.opt.foldexpr = "" -- set to "nvim_treesitter#foldexpr()" for treesitter based folding
vim.opt.guifont = "monospace:h17" -- the font used in graphical neovim applications
vim.opt.hidden = true -- required to keep multiple buffers and open multiple buffers
vim.opt.hlsearch = true -- highlight all matches on previous search pattern
vim.opt.ignorecase = true -- ignore case in search patterns
vim.opt.mouse = "a" -- allow the mouse to be used in neovim
vim.opt.pumheight = 10 -- pop up menu height
vim.opt.showmode = false -- we don't need to see things like -- INSERT -- anymore
vim.opt.showtabline = 2 -- always show tabs
vim.opt.smartcase = true -- smart case
vim.opt.smartindent = true -- make indenting smarter again
vim.opt.splitbelow = true -- force all horizontal splits to go below current window
vim.opt.splitright = true -- force all vertical splits to go to the right of current window
vim.opt.swapfile = false -- creates a swapfile
vim.opt.termguicolors = true -- set term gui colors (most terminals support this)
vim.opt.timeoutlen = 100 -- time to wait for a mapped sequence to complete (in milliseconds)
vim.opt.title = true -- set the title of window to the value of the titlestring
vim.opt.titlestring = "%<%F%=%l/%L - nvim" -- what the title of the window will be set to
vim.opt.undodir = vim.fn.stdpath "cache" .. "/undo"
vim.opt.undofile = true -- enable persistent undo
vim.opt.updatetime = 300 -- faster completion
vim.opt.writebackup = false -- if a file is being edited by another program (or was written to file while editing with another program) it is not allowed to be edited
vim.opt.expandtab = true -- convert tabs to spaces
vim.opt.shiftwidth = 2 -- the number of spaces inserted for each indentation
vim.opt.tabstop = 2 -- insert 2 spaces for a tab
vim.opt.cursorline = true -- highlight the current line
vim.opt.number = true -- set numbered lines
vim.opt.relativenumber = false -- set relative numbered lines
vim.opt.numberwidth = 4 -- set number column width to 2 {default 4}
vim.opt.signcolumn = "yes" -- always show the sign column otherwise it would shift the text each time
vim.opt.wrap = false -- display lines as one long line
vim.opt.spell = false
vim.opt.spelllang = "en"
vim.opt.scrolloff = 8 -- is one of my fav
vim.opt.sidescrolloff = 8

View File

@@ -0,0 +1,145 @@
local generic_opts_any = { noremap = true, silent = true }
local generic_opts = {
insert_mode = generic_opts_any,
normal_mode = generic_opts_any,
visual_mode = generic_opts_any,
visual_block_mode = generic_opts_any,
command_mode = generic_opts_any,
term_mode = { silent = true },
}
local mode_adapters = {
insert_mode = "i",
normal_mode = "n",
term_mode = "t",
visual_mode = "v",
visual_block_mode = "x",
command_mode = "c",
}
---@class Keys
---@field insert_mode table
---@field normal_mode table
---@field terminal_mode table
---@field visual_mode table
---@field visual_block_mode table
---@field command_mode table
local defaults = {
insert_mode = {
-- Move current line / block with Alt-j/k ala vscode.
["<A-j>"] = "<Esc>:m .+1<CR>==gi",
-- Move current line / block with Alt-j/k ala vscode.
["<A-k>"] = "<Esc>:m .-2<CR>==gi",
-- navigation
["<A-Up>"] = "<C-\\><C-N><C-w>k",
["<A-Down>"] = "<C-\\><C-N><C-w>j",
["<A-Left>"] = "<C-\\><C-N><C-w>h",
["<A-Right>"] = "<C-\\><C-N><C-w>l",
},
normal_mode = {
-- Better window movement
["<C-h>"] = "<C-w>h",
["<C-j>"] = "<C-w>j",
["<C-k>"] = "<C-w>k",
["<C-l>"] = "<C-w>l",
-- Resize with arrows
["<C-Up>"] = ":resize -2<CR>",
["<C-Down>"] = ":resize +2<CR>",
["<C-Left>"] = ":vertical resize -2<CR>",
["<C-Right>"] = ":vertical resize +2<CR>",
-- Move current line / block with Alt-j/k a la vscode.
["<A-j>"] = ":m .+1<CR>==",
["<A-k>"] = ":m .-2<CR>==",
-- QuickFix
["]q"] = ":cnext<CR>",
["[q"] = ":cprev<CR>",
["<C-q>"] = ":call QuickFixToggle()<CR>",
},
term_mode = {
-- Terminal window navigation
["<C-h>"] = "<C-\\><C-N><C-w>h",
["<C-j>"] = "<C-\\><C-N><C-w>j",
["<C-k>"] = "<C-\\><C-N><C-w>k",
["<C-l>"] = "<C-\\><C-N><C-w>l",
},
visual_mode = {
-- Better indenting
["<"] = "<gv",
[">"] = ">gv",
-- ["p"] = '"0p',
-- ["P"] = '"0P',
},
visual_block_mode = {
-- Move current line / block with Alt-j/k ala vscode.
["<A-j>"] = ":m '>+1<CR>gv-gv",
["<A-k>"] = ":m '<-2<CR>gv-gv",
},
command_mode = {
-- navigate tab completion with <c-j> and <c-k>
-- runs conditionally
["<C-j>"] = { 'pumvisible() ? "\\<C-n>" : "\\<C-j>"', { expr = true, noremap = true } },
["<C-k>"] = { 'pumvisible() ? "\\<C-p>" : "\\<C-k>"', { expr = true, noremap = true } },
},
}
if vim.fn.has "mac" == 1 then
defaults.normal_mode["<A-Up>"] = defaults.normal_mode["<C-Up>"]
defaults.normal_mode["<A-Down>"] = defaults.normal_mode["<C-Down>"]
defaults.normal_mode["<A-Left>"] = defaults.normal_mode["<C-Left>"]
defaults.normal_mode["<A-Right>"] = defaults.normal_mode["<C-Right>"]
Log:debug "Activated mac keymappings"
end
function set_keymaps(mode, key, val)
local opt = generic_opts[mode] or generic_opts_any
if type(val) == "table" then
opt = val[2]
val = val[1]
end
if val then
vim.keymap.set(mode, key, val, opt)
else
pcall(vim.api.nvim_del_keymap, mode, key)
end
end
function load_mode(mode, keymaps)
mode = mode_adapters[mode] or mode
for k, v in pairs(keymaps) do
set_keymaps(mode, k, v)
end
end
function load(keymaps)
keymaps = keymaps or {}
for mode, mapping in pairs(keymaps) do
load_mode(mode, mapping)
end
end
function load_defaults()
load(get_defaults())
keys = keys or {}
for idx, _ in pairs(defaults) do
if not keys[idx] then
keys[idx] = {}
end
end
end
function get_defaults()
return defaults
end
load_defaults()

View File

@@ -0,0 +1,50 @@
local status, lspc = pcall(require, 'lspconfig')
if (not status) then return end
lspc.clangd.setup{}
local buf_map = function(bufnr, mode, lhs, rhs, opts)
vim.api.nvim_buf_set_keymap(bufnr, mode, lhs, rhs, opts or {
silent = true,
})
end
local protocol = require('vim.lsp.protocol')
local on_attach = function(client, buffnr)
if client.server.capabilities.documentFormattingProvider then
vimapi.nvim_create_autocmd("BufWritePre", {
group = vim.api.nvim_create_augroup("format", { clear = true }),
buffer = buffnr,
callback = function() vim.lsp.buf.formatting_seq_sync() end
})
end
end
local capabilities = vim.lsp.protocol.make_client_capabilities()
capabilities.textDocument.completion.completionItem.snippetSupport = true
capabilities = require('cmp_nvim_lsp').default_capabilities(capabilities)
local servers = { 'tsserver', 'sumneko_lua', 'cssls', 'yamlls', 'intelephense' }
for _, lsp in pairs(servers) do
require('lspconfig')[lsp].setup {
-- on_attach = on_attach,
capabilities = capabilities,
}
end
lspc.sumneko_lua.setup({
settings = {
Lua = {
diagnostics = {
globals = { 'vim' }
},
workspace = {
library = vim.api.nvim_get_runtime_file("", true),
checkThirdParty = false
},
},
},
})
-- lspc.intelephense.setup()

View File

@@ -0,0 +1,73 @@
local has_words_before = function()
local line, col = unpack(vim.api.nvim_win_get_cursor(0))
return col ~= 0 and vim.api.nvim_buf_get_lines(0, line - 1, line, true)[1]:sub(col, col):match("%s") == nil
end
local feedkey = function(key, mode)
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes(key, true, true, true), mode, true)
end
local cmp = require("cmp")
local lspkind = require("lspkind")
cmp.setup({
sources = {
{ name = "nvim_lsp" },
{ name = "cmp_tabnine" },
{ name = "treesitter" },
{ name = "buffer" },
{ name = "path" },
{ name = "vsnip" },
-- { name = "copilot" },
},
snippet = {
expand = function(args)
vim.fn["vsnip#anonymous"](args.body)
end,
},
formatting = {
format = lspkind.cmp_format({
with_text = true,
menu = {
buffer = "[Buf]",
nvim_lsp = "[LSP]",
nvim_lua = "[Lua]",
latex_symbols = "[Latex]",
treesitter = "[TS]",
cmp_tabnine = "[TN]",
vsnip = "[Snip]",
},
}),
},
mapping = {
["<CR>"] = cmp.mapping.confirm({ select = true }),
["<Tab>"] = cmp.mapping(function(fallback)
if cmp.visible() then
cmp.select_next_item()
elseif vim.fn["vsnip#available"](1) == 1 then
feedkey("<Plug>(vsnip-expand-or-jump)", "")
elseif has_words_before() then
cmp.complete()
else
fallback()
end
end, {
"i",
"s",
}),
["<S-Tab>"] = cmp.mapping(function()
if cmp.visible() then
cmp.select_prev_item()
elseif vim.fn["vsnip#jumpable"](-1) == 1 then
feedkey("<Plug>(vsnip-jump-prev)", "")
end
end, {
"i",
"s",
}),
},
})

View File

@@ -0,0 +1,41 @@
config = {
---@usage set to false to disable project.nvim.
--- This is on by default since it's currently the expected behavior.
active = true,
on_config_done = nil,
---@usage set to true to disable setting the current-woriking directory
--- Manual mode doesn't automatically change your root directory, so you have
--- the option to manually do so using `:ProjectRoot` command.
manual_mode = false,
---@usage Methods of detecting the root directory
--- Allowed values: **"lsp"** uses the native neovim lsp
--- **"pattern"** uses vim-rooter like glob pattern matching. Here
--- order matters: if one is not detected, the other is used as fallback. You
--- can also delete or rearangne the detection methods.
-- detection_methods = { "lsp", "pattern" }, -- NOTE: lsp detection will get annoying with multiple langs in one project
detection_methods = { "pattern" },
---@usage patterns used to detect root dir, when **"pattern"** is in detection_methods
patterns = { ".git", "_darcs", ".hg", ".bzr", ".svn", "Makefile", "package.json", "pom.xml" },
---@ Show hidden files in telescope when searching for files in a project
show_hidden = false,
---@usage When set to false, you will get a message when project.nvim changes your directory.
-- When set to false, you will get a message when project.nvim changes your directory.
silent_chdir = true,
---@usage list of lsp client names to ignore when using **lsp** detection. eg: { "efm", ... }
ignore_lsp = {},
}
local status_ok, project = pcall(require, "project_nvim")
if not status_ok then
return
end
project.setup(config)

View File

@@ -0,0 +1,148 @@
local function get_pickers(actions)
return {
find_files = {
theme = "dropdown",
hidden = true,
previewer = false,
},
live_grep = {
--@usage don't include the filename in the search results
only_sort_text = true,
theme = "dropdown",
},
grep_string = {
only_sort_text = true,
theme = "dropdown",
},
buffers = {
theme = "dropdown",
previewer = false,
initial_mode = "normal",
mappings = {
i = {
["<C-d>"] = actions.delete_buffer,
},
n = {
["dd"] = actions.delete_buffer,
},
},
},
planets = {
show_pluto = true,
show_moon = true,
},
git_files = {
theme = "dropdown",
hidden = true,
previewer = false,
show_untracked = true,
},
lsp_references = {
theme = "dropdown",
initial_mode = "normal",
},
lsp_definitions = {
theme = "dropdown",
initial_mode = "normal",
},
lsp_declarations = {
theme = "dropdown",
initial_mode = "normal",
},
lsp_implementations = {
theme = "dropdown",
initial_mode = "normal",
},
}
end
local ok, actions = pcall(require, "telescope.actions")
if not ok then
return
end
local config = {
prompt_prefix = " ",
selection_caret = " ",
entry_prefix = " ",
initial_mode = "insert",
selection_strategy = "reset",
sorting_strategy = "descending",
layout_strategy = "horizontal",
layout_config = {
width = 0.75,
preview_cutoff = 120,
horizontal = {
preview_width = function(_, cols, _)
if cols < 120 then
return math.floor(cols * 0.8)
end
return math.floor(cols * 0.8)
end,
mirror = false,
},
vertical = { mirror = false },
},
vimgrep_arguments = {
"rg",
"--color=never",
"--no-heading",
"--with-filename",
"--line-number",
"--column",
"--smart-case",
"--hidden",
"--glob=!.git/",
},
---@usage Mappings are fully customizable. Many familiar mapping patterns are setup as defaults.
mappings = {
i = {
["<C-n>"] = actions.move_selection_next,
["<C-p>"] = actions.move_selection_previous,
["<C-c>"] = actions.close,
["<C-j>"] = actions.cycle_history_next,
["<C-k>"] = actions.cycle_history_prev,
["<C-q>"] = actions.smart_send_to_qflist + actions.open_qflist,
["<CR>"] = actions.select_default,
},
n = {
["<C-n>"] = actions.move_selection_next,
["<C-p>"] = actions.move_selection_previous,
["<C-q>"] = actions.smart_send_to_qflist + actions.open_qflist,
},
},
pickers = get_pickers(actions),
file_ignore_patterns = {},
path_display = { "smart" },
winblend = 0,
border = {},
borderchars = { "", "", "", "", "", "", "", "" },
color_devicons = true,
set_env = { ["COLORTERM"] = "truecolor" }, -- default = nil,
extensions = {
fzf = {
fuzzy = true, -- false will only do exact matching
override_generic_sorter = true, -- override the generic sorter
override_file_sorter = true, -- override the file sorter
case_mode = "smart_case", -- or "ignore_case" or "respect_case"
},
},
}
local previewers = require "telescope.previewers"
local sorters = require "telescope.sorters"
config = vim.tbl_extend("keep", {
file_previewer = previewers.vim_buffer_cat.new,
grep_previewer = previewers.vim_buffer_vimgrep.new,
qflist_previewer = previewers.vim_buffer_qflist.new,
file_sorter = sorters.get_fuzzy_file,
generic_sorter = sorters.get_generic_fuzzy_sorter,
}, config)
local telescope = require "telescope"
telescope.setup(config)
require("telescope").load_extension "projects"
require("telescope").load_extension "fzf"

View File

@@ -0,0 +1,133 @@
local config = {
active = true,
on_config_done = nil,
-- size can be a number or function which is passed the current terminal
size = 20,
open_mapping = [[<c-t>]],
hide_numbers = true, -- hide the number column in toggleterm buffers
shade_filetypes = {},
shade_terminals = true,
shading_factor = 2, -- the degree by which to darken to terminal colour, default: 1 for dark backgrounds, 3 for light
start_in_insert = true,
insert_mappings = true, -- whether or not the open mapping applies in insert mode
persist_size = false,
-- direction = 'vertical' | 'horizontal' | 'window' | 'float',
direction = "float",
close_on_exit = true, -- close the terminal window when the process exits
shell = vim.o.shell, -- change the default shell
-- This field is only relevant if direction is set to 'float'
float_opts = {
-- The border key is *almost* the same as 'nvim_win_open'
-- see :h nvim_win_open for details on borders however
-- the 'curved' border is a custom border type
-- not natively supported but implemented in this plugin.
-- border = 'single' | 'double' | 'shadow' | 'curved' | ... other options supported by win open
border = "curved",
-- width = <value>,
-- height = <value>,
winblend = 0,
highlights = {
border = "Normal",
background = "Normal",
},
},
-- Add executables on the config.lua
-- { exec, keymap, name}
-- lvim.builtin.terminal.execs = {{}} to overwrite
-- lvim.builtin.terminal.execs[#lvim.builtin.terminal.execs+1] = {"gdb", "tg", "GNU Debugger"}
-- TODO: pls add mappings in which key and refactor this
execs = {
{ vim.o.shell, "<M-1>", "Horizontal Terminal", "horizontal", 0.3 },
{ vim.o.shell, "<M-2>", "Vertical Terminal", "vertical", 0.4 },
{ vim.o.shell, "<M-3>", "Float Terminal", "float", nil },
},
}
--- Get current buffer size
---@return {width: number, height: number}
local function get_buf_size()
local cbuf = vim.api.nvim_get_current_buf()
local bufinfo = vim.tbl_filter(function(buf)
return buf.bufnr == cbuf
end, vim.fn.getwininfo(vim.api.nvim_get_current_win()))[1]
if bufinfo == nil then
return { width = -1, height = -1 }
end
return { width = bufinfo.width, height = bufinfo.height }
end
--- Get the dynamic terminal size in cells
---@param direction number
---@param size integer
---@return integer
local function get_dynamic_terminal_size(direction, size)
size = size or config.size
if direction ~= "float" and tostring(size):find(".", 1, true) then
size = math.min(size, 1.0)
local buf_sizes = get_buf_size()
local buf_size = direction == "horizontal" and buf_sizes.height or buf_sizes.width
return buf_size * size
else
return size
end
end
Add_exec = function(opts)
local binary = opts.cmd:match "(%S+)"
if vim.fn.executable(binary) ~= 1 then
Log:debug("Skipping configuring executable " .. binary .. ". Please make sure it is installed properly.")
return
end
vim.keymap.set({ "n", "t" }, opts.keymap, function()
M._exec_toggle { cmd = opts.cmd, count = opts.count, direction = opts.direction, size = opts.size() }
end, { desc = opts.label, noremap = true, silent = true })
end
local terminal = require "toggleterm"
terminal.setup(config)
for i, exec in pairs(config.execs) do
local direction = exec[4] or config.direction
local opts = {
cmd = exec[1],
keymap = exec[2],
label = exec[3],
-- NOTE: unable to consistently bind id/count <= 9, see #2146
count = i + 100,
direction = direction,
size = function()
return get_dynamic_terminal_size(direction, exec[5])
end,
}
Add_exec(opts)
end
_exec_toggle = function(opts)
local Terminal = require("toggleterm.terminal").Terminal
local term = Terminal:new { cmd = opts.cmd, count = opts.count, direction = opts.direction }
term:toggle(opts.size, opts.direction)
end
Lazygit_toggle = function()
local Terminal = require("toggleterm.terminal").Terminal
local lazygit = Terminal:new {
cmd = "lazygit",
hidden = true,
direction = "float",
float_opts = {
border = "none",
width = 100000,
height = 100000,
},
on_open = function(_)
vim.cmd "startinsert!"
end,
on_close = function(_) end,
count = 99,
}
lazygit:toggle()
end

View File

@@ -0,0 +1,18 @@
-- set colorscheme
vim.cmd 'set termguicolors'
require("catppuccin").setup()
vim.cmd [[colorscheme dracula]]
-- enable colorizer
require'colorizer'.setup()
-- set sign
vim.cmd 'sign define DiagnosticSignError text= linehl= texthl=DiagnosticSignError numhl='
vim.cmd 'sign define DiagnosticSignHint text= linehl= texthl=DiagnosticSignHint numhl='
vim.cmd 'sign define DiagnosticSignInfo text= linehl= texthl=DiagnosticSignInfo numhl='
vim.cmd 'sign define DiagnosticSignWarn text= linehl= texthl=DiagnosticSignWarn numhl='
-- set lightline theme to horizon
vim.g.lightline = { colorscheme = "dracula" }

View File

@@ -0,0 +1,14 @@
require'nvim-treesitter.configs'.setup {
textobjects = {
select = {
enable = true,
keymaps = {
-- You can use the capture groups defined in textobjects.scm
["af"] = "@function.outer",
["if"] = "@function.inner",
["ac"] = "@class.outer",
["ic"] = "@class.inner",
},
},
},
}

View File

@@ -0,0 +1,30 @@
require("nvim-treesitter.configs").setup({
highlight = {
enable = true,
disable = {},
},
rainbow = {
enable = true,
extended_mode = true,
},
autotag = {
enable = true,
},
context_commentstring = {
enable = true,
},
incremental_selection = {
enable = true,
keymaps = {
init_selection = "gnn",
node_incremental = "grn",
scope_incremental = "grc",
node_decremental = "grm",
},
},
})
-- breaks highlight
-- vim.cmd([[set foldmethod=expr]])
-- vim.cmd([[set foldlevel=10]])
-- vim.cmd([[set foldexpr=nvim_treesitter#foldexpr()]])

View File

@@ -0,0 +1,26 @@
-- null-ls
local nb = require('null-ls').builtins
require('null-ls').setup({
sources = {
nb.formatting.alejandra,
nb.code_actions.statix,
nb.diagnostics.cppcheck,
nb.diagnostics.deadnix,
nb.diagnostics.statix,
nb.diagnostics.eslint,
nb.completion.spell,
},
})
require("gitsigns").setup()
-- autopairs
require('nvim-autopairs').setup{}
-- copy to system clipboard
vim.api.nvim_set_keymap( 'v', '<Leader>y', '"+y', {noremap = true})
vim.api.nvim_set_keymap( 'n', '<Leader>y', ':%+y<CR>', {noremap = true})
-- paste from system clipboard
vim.api.nvim_set_keymap( 'n', '<Leader>p', '"+p', {noremap = true})

View File

@@ -0,0 +1,147 @@
vim.g.mapleader = " "
local function smart_quit()
local bufnr = vim.api.nvim_get_current_buf()
local modified = vim.api.nvim_buf_get_option(bufnr, "modified")
if modified then
vim.ui.input({
prompt = "You have unsaved changes. Quit anyway? (y/n) ",
}, function(input)
if input == "y" then
vim.cmd "q!"
end
end)
else
vim.cmd "q!"
end
end
local function find_project_files()
local _, builtin = pcall(require, "telescope.builtin")
local ok = pcall(builtin.git_files)
if not ok then
builtin.find_files()
end
end
local wk = require("which-key")
wk.setup({})
wk.register({
["<leader>"] = {
[";"] = { "<cmd>Alpha<CR>", "Dashboard" },
["w"] = { "<cmd>w!<CR>", "Save" },
["q"] = { "<cmd>smart_quit()<CR>", "Quit" },
["/"] = { "<Plug>(comment_toggle_linewise_current)", "Comment toggle current line" },
["c"] = { "<cmd>BufferKill<CR>", "Close Buffer" },
["f"] = { find_project_files, "Find File" },
["h"] = { "<cmd>nohlsearch<CR>", "No Highlight" },
b = {
name = "Buffers",
j = { "<cmd>BufferLinePick<cr>", "Jump" },
f = { "<cmd>Telescope buffers<cr>", "Find" },
b = { "<cmd>BufferLineCyclePrev<cr>", "Previous" },
n = { "<cmd>BufferLineCycleNext<cr>", "Next" },
-- w = { "<cmd>BufferWipeout<cr>", "Wipeout" }, -- TODO: implement this for bufferline
e = {
"<cmd>BufferLinePickClose<cr>",
"Pick which buffer to close",
},
h = { "<cmd>BufferLineCloseLeft<cr>", "Close all to the left" },
l = {
"<cmd>BufferLineCloseRight<cr>",
"Close all to the right",
},
D = {
"<cmd>BufferLineSortByDirectory<cr>",
"Sort by directory",
},
L = {
"<cmd>BufferLineSortByExtension<cr>",
"Sort by language",
},
},
-- " Available Debug Adapters:
-- " https://microsoft.github.io/debug-adapter-protocol/implementors/adapters/
-- " Adapter configuration and installation instructions:
-- " https://github.com/mfussenegger/nvim-dap/wiki/Debug-Adapter-installation
-- " Debug Adapter protocol:
-- " https://microsoft.github.io/debug-adapter-protocol/
-- " Debugging
g = {
name = "Git",
g = { Lazygit_toggle, "Lazygit" },
j = { "<cmd>lua require 'gitsigns'.next_hunk({navigation_message = false})<cr>", "Next Hunk" },
k = { "<cmd>lua require 'gitsigns'.prev_hunk({navigation_message = false})<cr>", "Prev Hunk" },
l = { "<cmd>lua require 'gitsigns'.blame_line()<cr>", "Blame" },
p = { "<cmd>lua require 'gitsigns'.preview_hunk()<cr>", "Preview Hunk" },
r = { "<cmd>lua require 'gitsigns'.reset_hunk()<cr>", "Reset Hunk" },
R = { "<cmd>lua require 'gitsigns'.reset_buffer()<cr>", "Reset Buffer" },
s = { "<cmd>lua require 'gitsigns'.stage_hunk()<cr>", "Stage Hunk" },
u = {
"<cmd>lua require 'gitsigns'.undo_stage_hunk()<cr>",
"Undo Stage Hunk",
},
o = { "<cmd>Telescope git_status<cr>", "Open changed file" },
b = { "<cmd>Telescope git_branches<cr>", "Checkout branch" },
c = { "<cmd>Telescope git_commits<cr>", "Checkout commit" },
C = {
"<cmd>Telescope git_bcommits<cr>",
"Checkout commit(for current file)",
},
d = {
"<cmd>Gitsigns diffthis HEAD<cr>",
"Git Diff",
},
},
l = {
name = "LSP",
a = { "<cmd>lua vim.lsp.buf.code_action()<cr>", "Code Action" },
d = { "<cmd>Telescope diagnostics bufnr=0 theme=get_ivy<cr>", "Buffer Diagnostics" },
w = { "<cmd>Telescope diagnostics<cr>", "Diagnostics" },
-- f = { require("lvim.lsp.utils").format, "Format" },
i = { "<cmd>LspInfo<cr>", "Info" },
I = { "<cmd>Mason<cr>", "Mason Info" },
j = {
vim.diagnostic.goto_next,
"Next Diagnostic",
},
k = {
vim.diagnostic.goto_prev,
"Prev Diagnostic",
},
l = { vim.lsp.codelens.run, "CodeLens Action" },
q = { vim.diagnostic.setloclist, "Quickfix" },
r = { vim.lsp.buf.rename, "Rename" },
s = { "<cmd>Telescope lsp_document_symbols<cr>", "Document Symbols" },
S = {
"<cmd>Telescope lsp_dynamic_workspace_symbols<cr>",
"Workspace Symbols",
},
e = { "<cmd>Telescope quickfix<cr>", "Telescope Quickfix" },
},
a = { "<cmd>lua require('telescope.builtin').lsp_code_actions()<cr>", "Code Actions" },
d = { "<cmd>lua require('telescope.builtin').lsp_document_diagnostics()<cr>", "LSP Diagnostics" },
k = { "<cmd>lua vim.lsp.buf.signature_help()<cr>", "Signature Help" },
P = { "<cmd>lua require'telescope'.extensions.projects.projects{}<cr>", "Signature Help" },
}
})
wk.register(
{
["/"] = { "<Plug>(comment_toggle_linewise_visual)", "Comment toggle linewise (visual)" },
},
{
mode = "v", -- VISUAL mode
prefix = "<leader>",
buffer = nil, -- Global mappings. Specify a buffer number for buffer local mappings
silent = true, -- use `silent` when creating keymaps
noremap = true, -- use `noremap` when creating keymaps
nowait = true, -- use `nowait` when creating keymaps
}
)

View File

@@ -0,0 +1,83 @@
{ pkgs, ... }:
{
environment.variables = { EDITOR = "vim"; };
environment.systemPackages = with pkgs; [
nodePackages.typescript-language-server
sumneko-lua-language-server
nodePackages.intelephense
nodePackages.vscode-css-languageserver-bin
nodePackages.yaml-language-server
gopls
lazygit
(neovim.override {
vimAlias = true;
configure = {
packages.myPlugins = with pkgs.vimPlugins; {
start = [
bufferline-nvim
catppuccin-nvim
cmp-buffer
cmp-nvim-lsp
cmp-path
cmp-spell
cmp-treesitter
cmp-vsnip
comment-nvim
dracula-vim
friendly-snippets
gitsigns-nvim
lightline-vim
lspkind-nvim
neogit
null-ls-nvim
nvim-autopairs
nvim-cmp
nvim-colorizer-lua
nvim-lspconfig
nvim-tree-lua
nvim-ts-rainbow
pkgs.vimPlugins.nvim-treesitter.withAllGrammars
# (nvim-treesitter.withPlugins (_: pkgs.tree-sitter.allGrammars))
plenary-nvim
project-nvim
telescope-fzf-native-nvim
telescope-nvim
toggleterm-nvim
vim-floaterm
vim-sneak
vim-vsnip
which-key-nvim
];
opt = [];
};
customRC = let
luaRequire = module:
builtins.readFile (builtins.toString
./config
+ "/${module}.lua");
luaConfig = builtins.concatStringsSep "\n" (map luaRequire [
"init"
"keymappings"
"icons"
"lspconfig"
"nvim-cmp"
"theming"
"project"
"telescope"
"terminal"
"treesitter"
"treesitter-textobjects"
"utils"
"bufferline"
"which-key"
]);
in ''
lua << EOF
${luaConfig}
EOF
'';
};
}
)];
}

325
utils/modules/openldap.nix Normal file
View File

@@ -0,0 +1,325 @@
{
pkgs,
config,
...
}: {
services.openldap = {
enable = true;
settings.attrs.olcLogLevel = "0";
settings.attrs.olcTLSCACertificateFile = config.sops.secrets.openldap-ca.path;
settings.attrs.olcTLSCertificateFile = config.sops.secrets.openldap-cert.path;
settings.attrs.olcTLSCertificateKeyFile = config.sops.secrets.openldap-key.path;
settings.children = {
"cn=schema".includes = [
"${pkgs.openldap}/etc/schema/core.ldif"
"${pkgs.openldap}/etc/schema/cosine.ldif"
"${pkgs.openldap}/etc/schema/inetorgperson.ldif"
"${pkgs.openldap}/etc/schema/nis.ldif"
];
"olcDatabase={1}mdb".attrs = {
objectClass = ["olcDatabaseConfig" "olcMdbConfig"];
olcDatabase = "{1}mdb";
olcDbDirectory = "/var/lib/openldap/data";
olcRootPW.path = config.sops.secrets.openldap-rootpw.path;
olcRootDN = "cn=admin,dc=cloonar,dc=com";
olcSuffix = "dc=cloonar,dc=com";
olcAccess = [
''
{0}to attrs=userPassword
by self write by anonymous auth
by dn.base="cn=dovecot,dc=cloonar,dc=com" read
by dn.base="cn=gogs,ou=system,ou=users,dc=cloonar,dc=com" read
read by * none
''
''
{1}to attrs=loginShell
by self write
by * read
''
''
{2}to dn.subtree="ou=system,ou=users,dc=cloonar,dc=com"
by dn.base="cn=dovecot,dc=mail,dc=cloonar,dc=com" read
by dn.subtree="ou=system,ou=users,dc=cloonar,dc=com" read
by * none
''
''
{3}to dn.subtree="ou=jabber,ou=users,dc=cloonar,dc=com"
by dn.base="cn=prosody,ou=system,ou=users,dc=eve" write
by * read
''
''
{4}to * by * read
''
];
};
"olcOverlay=syncprov,olcDatabase={1}mdb".attrs = {
objectClass = ["olcOverlayConfig" "olcSyncProvConfig"];
olcOverlay = "syncprov";
olcSpSessionLog = "100";
};
"olcDatabase={2}monitor".attrs = {
olcDatabase = "{2}monitor";
objectClass = ["olcDatabaseConfig" "olcMonitorConfig"];
olcAccess = [
''
{0}to *
by dn.exact="cn=netdata,ou=system,ou=users,dc=cloonar,dc=com" read
by * none
''
];
};
"cn={1}bitwarden,cn=schema" = {
attrs = {
cn = "{1}bitwarden";
objectClass = "olcSchemaConfig";
olcObjectClasses = [
''
(1.3.6.1.4.1.28298.1.2.4 NAME 'bitwarden'
SUP uidObject AUXILIARY
DESC 'Added to an account to allow bitwarden access'
MUST (mail $ userPassword))
''
]
};
};
# "cn={1}squid,cn=schema".attrs = {
# cn = "{1}squid";
# objectClass = "olcSchemaConfig";
# olcObjectClasses = [
# ''
# (1.3.6.1.4.1.16548.1.2.4 NAME 'proxyUser'
# SUP top AUXILIARY
# DESC 'Account to allow a user to use the Squid proxy'
# MUST ( mail $ userPassword ))
# ''
# ];
# };
# "cn={1}grafana,cn=schema".attrs = {
# cn = "{1}grafana";
# objectClass = "olcSchemaConfig";
# olcObjectClasses = [
# ''
# (1.3.6.1.4.1.28293.1.2.5 NAME 'grafana'
# SUP uidObject AUXILIARY
# DESC 'Added to an account to allow grafana access'
# MUST (mail))
# ''
# ];
# };
"cn={2}postfix,cn=schema".attrs = {
cn = "{2}postfix";
objectClass = "olcSchemaConfig";
olcAttributeTypes = [
''
(1.3.6.1.4.1.12461.1.1.1 NAME 'postfixTransport'
DESC 'A string directing postfix which transport to use'
EQUALITY caseExactIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{20} SINGLE-VALUE)''
''
(1.3.6.1.4.1.12461.1.1.5 NAME 'mailbox'
DESC 'The absolute path to the mailbox for a mail account in a non-default location'
EQUALITY caseExactIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE)
''
''
(1.3.6.1.4.1.12461.1.1.6 NAME 'quota'
DESC 'A string that represents the quota on a mailbox'
EQUALITY caseExactIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE)
''
''
(1.3.6.1.4.1.12461.1.1.8 NAME 'maildrop'
DESC 'RFC822 Mailbox - mail alias'
EQUALITY caseIgnoreIA5Match
SUBSTR caseIgnoreIA5SubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256})
''
];
olcObjectClasses = [
''
(1.3.6.1.4.1.12461.1.2.1 NAME 'mailAccount'
SUP top AUXILIARY
DESC 'Mail account objects'
MUST ( mail $ userPassword )
MAY ( cn $ description $ quota))
''
''
(1.3.6.1.4.1.12461.1.2.2 NAME 'mailAlias'
SUP top STRUCTURAL
DESC 'Mail aliasing/forwarding entry'
MUST ( mail $ maildrop )
MAY ( cn $ description ))
''
''
(1.3.6.1.4.1.12461.1.2.3 NAME 'mailDomain'
SUP domain STRUCTURAL
DESC 'Virtual Domain entry to be used with postfix transport maps'
MUST ( dc )
MAY ( postfixTransport $ description ))
''
''
(1.3.6.1.4.1.12461.1.2.4 NAME 'mailPostmaster'
SUP top AUXILIARY
DESC 'Added to a mailAlias to create a postmaster entry'
MUST roleOccupant)
''
];
};
"cn={1}openssh,cn=schema".attrs = {
cn = "{1}openssh";
objectClass = "olcSchemaConfig";
olcAttributeTypes = [
''
(1.3.6.1.4.1.24552.500.1.1.1.13
NAME 'sshPublicKey'
DESC 'MANDATORY: OpenSSH Public key'
EQUALITY octetStringMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )
''
];
olcObjectClasses = [
''
(1.3.6.1.4.1.24552.500.1.1.2.0
NAME 'ldapPublicKey'
SUP top AUXILIARY
DESC 'MANDATORY: OpenSSH LPK objectclass'
MUST ( sshPublicKey $ uid ))
''
];
};
"cn={1}nginx,cn=schema".attrs = {
cn = "{1}nginx";
objectClass = "olcSchemaConfig";
olcObjectClasses = [
''
(1.3.6.1.4.1.28295.1.2.4 NAME 'nginx'
SUP top AUXILIARY
DESC 'Added to an account to allow nginx access'
MUST ( mail $ userPassword ))
''
];
};
"cn={1}nextcloud,cn=schema".attrs = {
cn = "{1}nextcloud";
objectClass = "olcSchemaConfig";
olcAttributeTypes = [
''
(1.3.6.1.4.1.39430.1.1.1
NAME 'ownCloudQuota'
DESC 'User Quota (e.g. 15 GB)'
SYNTAX '1.3.6.1.4.1.1466.115.121.1.15')
''
];
olcObjectClasses = [
''
(1.3.6.1.4.1.39430.1.2.1
NAME 'ownCloud'
DESC 'ownCloud LDAP Schema'
AUXILIARY
MUST ( mail $ userPassword )
MAY ( ownCloudQuota ))
''
];
};
"cn={1}gogs,cn=schema".attrs = {
cn = "{1}gitlab";
objectClass = "olcSchemaConfig";
olcObjectClasses = [
''
( 1.3.6.1.4.1.28293.1.2.4 NAME 'gitlab'
SUP uidObject AUXILIARY
DESC 'Added to an account to allow gitlab access'
MUST (mail))
''
];
};
"cn={1}iobroker,cn=schema".attrs = {
cn = "{1}homeAssistant";
objectClass = "olcSchemaConfig";
olcObjectClasses = [
''
(1.3.6.1.4.1.28297.1.2.4 NAME 'homeAssistant'
SUP uidObject AUXILIARY
DESC 'Added to an account to allow home-assistant access'
MUST (mail) )
''
];
};
# "cn={1}ttrss,cn=schema".attrs = {
# cn = "{1}ttrss";
# objectClass = "olcSchemaConfig";
# olcObjectClasses = [
# ''
# ( 1.3.6.1.4.1.28294.1.2.4 NAME 'ttrss'
# SUP top AUXILIARY
# DESC 'Added to an account to allow tinytinyrss access'
# MUST ( mail $ userPassword ))
# ''
# ];
# };
# "cn={1}prometheus,cn=schema".attrs = {
# cn = "{1}prometheus";
# objectClass = "olcSchemaConfig";
# olcObjectClasses = [
# ''
# ( 1.3.6.1.4.1.28296.1.2.4
# NAME 'prometheus'
# SUP uidObject AUXILIARY
# DESC 'Added to an account to allow prometheus access'
# MUST (mail))
# ''
# ];
# };
# "cn={1}loki,cn=schema".attrs = {
# cn = "{1}loki";
# objectClass = "olcSchemaConfig";
# olcObjectClasses = [
# ''
# ( 1.3.6.1.4.1.28299.1.2.4
# NAME 'loki'
# SUP uidObject AUXILIARY
# DESC 'Added to an account to allow loki access'
# MUST (mail))
# ''
# ];
# };
# "cn={1}flood,cn=schema".attrs = {
# cn = "{1}flood";
# objectClass = "olcSchemaConfig";
# olcObjectClasses = [
# ''
# (1.3.6.1.4.1.28300.1.2.4 NAME 'flood'
# SUP uidObject AUXILIARY
# DESC 'Added to an account to allow flood access'
# MUST (mail))
# ''
# ];
# };
};
};
sops.secrets.openldap-rootpw = {
owner = "openldap";
sopsFile = ./secrets.yaml;
};
sops.secrets.openldap-ca = {
owner = "openldap";
sopsFile = ./secrets.yaml;
};
sops.secrets.openldap-cert = {
owner = "openldap";
sopsFile = ./secrets.yaml;
};
sops.secrets.openldap-key = {
owner = "openldap";
sopsFile = ./secrets.yaml;
};
networking.firewall.allowedTCPPorts = [636];
}

View File

@@ -0,0 +1,480 @@
{
pkgs,
config,
...
}:
let
domain = config.networking.domain;
# domain = "cloonar.com";
in {
services.openldap = {
enable = true;
urlList = [ "ldap:///" "ldaps:///" ];
settings.attrs = {
olcLogLevel = "-1";
olcTLSCACertificateFile = "/var/lib/acme/ldap.${domain}/full.pem";
olcTLSCertificateFile = "/var/lib/acme/ldap.${domain}/cert.pem";
olcTLSCertificateKeyFile = "/var/lib/acme/ldap.${domain}/key.pem";
olcTLSCipherSuite = "HIGH:MEDIUM:+3DES:+RC4:+aNULL";
olcTLSCRLCheck = "none";
olcTLSVerifyClient = "never";
olcTLSProtocolMin = "3.1";
olcSecurity = "tls=1";
};
settings.children = {
"cn=schema".includes = [
"${pkgs.openldap}/etc/schema/core.ldif"
"${pkgs.openldap}/etc/schema/cosine.ldif"
"${pkgs.openldap}/etc/schema/inetorgperson.ldif"
"${pkgs.openldap}/etc/schema/nis.ldif"
];
"olcDatabase={1}mdb".attrs = {
objectClass = ["olcDatabaseConfig" "olcMdbConfig"];
olcDatabase = "{1}mdb";
olcDbDirectory = "/var/lib/openldap/data";
olcSuffix = "dc=cloonar,dc=com";
olcRootDN = "cn=admin,dc=cloonar,dc=com";
olcRootPW.path = config.sops.secrets.openldap-rootpw.path;
olcAccess = [
''
{0}to attrs=userPassword
by self write
by anonymous auth
by dn="cn=owncloud,ou=system,ou=users,dc=cloonar,dc=com" write
by dn.subtree="ou=system,ou=users,dc=cloonar,dc=com" read
by group.exact="cn=Administrators,ou=groups,dc=cloonar,dc=com" write
by * none
''
''
{1}to attrs=loginShell
by self write
by dn.subtree="ou=system,ou=users,dc=cloonar,dc=com" read
by group.exact="cn=Administrators,ou=groups,dc=cloonar,dc=com" write
by * none
''
''
{2}to dn.subtree="ou=system,ou=users,dc=cloonar,dc=com"
by dn.subtree="ou=system,ou=users,dc=cloonar,dc=com" read
by group.exact="cn=Administrators,ou=groups,dc=cloonar,dc=com" write
by * none
''
''
{3}to *
by dn.subtree="ou=system,ou=users,dc=cloonar,dc=com" read
by dn="cn=admin,dc=cloonar,dc=com" write
by group.exact="cn=Administrators,ou=groups,dc=cloonar,dc=com" write
by * none
''
];
};
"olcOverlay=memberof,olcDatabase={1}mdb".attrs = {
objectClass = [ "olcOverlayConfig" "olcMemberOf" ];
olcOverlay = "memberof";
olcMemberOfRefint = "TRUE";
};
"olcOverlay=ppolicy,olcDatabase={1}mdb".attrs = {
objectClass = [ "olcOverlayConfig" "olcPPolicyConfig" ];
olcOverlay = "ppolicy";
olcPPolicyHashCleartext = "TRUE";
};
# "olcOverlay=syncprov,olcDatabase={1}mdb".attrs = {
# objectClass = ["olcOverlayConfig" "olcSyncProvConfig"];
# olcOverlay = "syncprov";
# olcSpSessionLog = "100";
# };
"olcDatabase={2}monitor".attrs = {
olcDatabase = "{2}monitor";
objectClass = ["olcDatabaseConfig" "olcMonitorConfig"];
olcAccess = [
''
{0}to *
by dn.exact="cn=netdata,ou=system,ou=users,dc=cloonar,dc=com" read
by * none
''
];
};
"olcDatabase={3}mdb".attrs = {
objectClass = ["olcDatabaseConfig" "olcMdbConfig"];
olcDatabase = "{3}mdb";
olcDbDirectory = "/var/lib/openldap/data";
olcSuffix = "dc=ghetto,dc=at";
olcAccess = [
''
{0}to attrs=userPassword
by self write
by anonymous auth
by dn="cn=owncloud,ou=system,ou=users,dc=cloonar,dc=com" write
by dn.subtree="ou=system,ou=users,dc=cloonar,dc=com" read
by group.exact="cn=Administrators,ou=groups,dc=cloonar,dc=com" write
by * none
''
''
{1}to *
by dn.subtree="ou=system,ou=users,dc=cloonar,dc=com" read
by group.exact="cn=Administrators,ou=groups,dc=cloonar,dc=com" write
by * read
''
];
};
"olcOverlay=memberof,olcDatabase={3}mdb".attrs = {
objectClass = [ "olcOverlayConfig" "olcMemberOf" ];
olcOverlay = "memberof";
olcMemberOfRefint = "TRUE";
};
"olcOverlay=ppolicy,olcDatabase={3}mdb".attrs = {
objectClass = [ "olcOverlayConfig" "olcPPolicyConfig" ];
olcOverlay = "ppolicy";
olcPPolicyHashCleartext = "TRUE";
};
"olcDatabase={4}mdb".attrs = {
objectClass = ["olcDatabaseConfig" "olcMdbConfig"];
olcDatabase = "{4}mdb";
olcDbDirectory = "/var/lib/openldap/data";
olcSuffix = "dc=superbros,dc=tv";
olcAccess = [
''
{0}to attrs=userPassword
by self write
by anonymous auth
by dn="cn=owncloud,ou=system,ou=users,dc=cloonar,dc=com" write
by dn.subtree="ou=system,ou=users,dc=cloonar,dc=com" read
by group.exact="cn=Administrators,ou=groups,dc=cloonar,dc=com" write
by * none
''
''
{1}to *
by dn.subtree="ou=system,ou=users,dc=cloonar,dc=com" read
by group.exact="cn=Administrators,ou=groups,dc=cloonar,dc=com" write
by * read
''
];
};
"olcOverlay=memberof,olcDatabase={4}mdb".attrs = {
objectClass = [ "olcOverlayConfig" "olcMemberOf" ];
olcOverlay = "memberof";
olcMemberOfRefint = "TRUE";
};
"olcOverlay=ppolicy,olcDatabase={4}mdb".attrs = {
objectClass = [ "olcOverlayConfig" "olcPPolicyConfig" ];
olcOverlay = "ppolicy";
olcPPolicyHashCleartext = "TRUE";
};
"olcDatabase={5}mdb".attrs = {
objectClass = ["olcDatabaseConfig" "olcMdbConfig"];
olcDatabase = "{5}mdb";
olcDbDirectory = "/var/lib/openldap/data";
olcSuffix = "dc=optiprot,dc=eu";
olcAccess = [
''
{0}to attrs=userPassword
by self write
by anonymous auth
by dn="cn=owncloud,ou=system,ou=users,dc=cloonar,dc=com" write
by dn.subtree="ou=system,ou=users,dc=cloonar,dc=com" read
by group.exact="cn=Administrators,ou=groups,dc=cloonar,dc=com" write
by * none
''
''
{1}to *
by dn.subtree="ou=system,ou=users,dc=cloonar,dc=com" read
by group.exact="cn=Administrators,ou=groups,dc=cloonar,dc=com" write
by * read
''
];
};
"olcOverlay=memberof,olcDatabase={5}mdb".attrs = {
objectClass = [ "olcOverlayConfig" "olcMemberOf" ];
olcOverlay = "memberof";
olcMemberOfRefint = "TRUE";
};
"olcOverlay=ppolicy,olcDatabase={5}mdb".attrs = {
objectClass = [ "olcOverlayConfig" "olcPPolicyConfig" ];
olcOverlay = "ppolicy";
olcPPolicyHashCleartext = "TRUE";
};
"olcDatabase={6}mdb".attrs = {
objectClass = ["olcDatabaseConfig" "olcMdbConfig"];
olcDatabase = "{6}mdb";
olcDbDirectory = "/var/lib/openldap/data";
olcSuffix = "dc=szaku-consulting,dc=at";
olcAccess = [
''
{0}to attrs=userPassword
by self write
by anonymous auth
by dn="cn=owncloud,ou=system,ou=users,dc=cloonar,dc=com" write
by dn.subtree="ou=system,ou=users,dc=cloonar,dc=com" read
by group.exact="cn=Administrators,ou=groups,dc=cloonar,dc=com" write
by * none
''
''
{1}to *
by dn.subtree="ou=system,ou=users,dc=cloonar,dc=com" read
by group.exact="cn=Administrators,ou=groups,dc=cloonar,dc=com" write
by * read
''
];
};
"olcOverlay=memberof,olcDatabase={6}mdb".attrs = {
objectClass = [ "olcOverlayConfig" "olcMemberOf" ];
olcOverlay = "memberof";
olcMemberOfRefint = "TRUE";
};
"olcOverlay=ppolicy,olcDatabase={6}mdb".attrs = {
objectClass = [ "olcOverlayConfig" "olcPPolicyConfig" ];
olcOverlay = "ppolicy";
olcPPolicyHashCleartext = "TRUE";
};
# "cn=module{0},cn=config" = {
# attrs = {
# objectClass = "olcModuleList";
# cn = "module{0}";
# olcModuleLoad = "ppolicy.la";
# };
# };
"cn={3}cloonar,cn=schema" = {
attrs = {
cn = "{1}cloonar";
objectClass = "olcSchemaConfig";
olcObjectClasses = [
''
(1.3.6.1.4.1.28298.1.2.4 NAME 'cloonarUser'
SUP (mailAccount) AUXILIARY
DESC 'Cloonar Account'
MAY (sshPublicKey $ ownCloudQuota $ quota))
''
];
};
};
"cn={2}postfix,cn=schema".attrs = {
cn = "{2}postfix";
objectClass = "olcSchemaConfig";
olcAttributeTypes = [
''
(1.3.6.1.4.1.12461.1.1.1 NAME 'postfixTransport'
DESC 'A string directing postfix which transport to use'
EQUALITY caseExactIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{20} SINGLE-VALUE)''
''
(1.3.6.1.4.1.12461.1.1.5 NAME 'mailbox'
DESC 'The absolute path to the mailbox for a mail account in a non-default location'
EQUALITY caseExactIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE)
''
''
(1.3.6.1.4.1.12461.1.1.6 NAME 'quota'
DESC 'A string that represents the quota on a mailbox'
EQUALITY caseExactIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE)
''
''
(1.3.6.1.4.1.12461.1.1.8 NAME 'maildrop'
DESC 'RFC822 Mailbox - mail alias'
EQUALITY caseIgnoreIA5Match
SUBSTR caseIgnoreIA5SubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256})
''
];
olcObjectClasses = [
''
(1.3.6.1.4.1.12461.1.2.1 NAME 'mailAccount'
SUP top AUXILIARY
DESC 'Mail account objects'
MUST ( mail $ userPassword )
MAY ( cn $ description $ quota))
''
''
(1.3.6.1.4.1.12461.1.2.2 NAME 'mailAlias'
SUP top STRUCTURAL
DESC 'Mail aliasing/forwarding entry'
MUST ( mail $ maildrop )
MAY ( cn $ description ))
''
''
(1.3.6.1.4.1.12461.1.2.3 NAME 'mailDomain'
SUP domain STRUCTURAL
DESC 'Virtual Domain entry to be used with postfix transport maps'
MUST ( dc )
MAY ( postfixTransport $ description ))
''
''
(1.3.6.1.4.1.12461.1.2.4 NAME 'mailPostmaster'
SUP top AUXILIARY
DESC 'Added to a mailAlias to create a postmaster entry'
MUST roleOccupant)
''
];
};
"cn={1}openssh,cn=schema".attrs = {
cn = "{1}openssh";
objectClass = "olcSchemaConfig";
olcAttributeTypes = [
''
(1.3.6.1.4.1.24552.500.1.1.1.13
NAME 'sshPublicKey'
DESC 'MANDATORY: OpenSSH Public key'
EQUALITY octetStringMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )
''
];
olcObjectClasses = [
''
(1.3.6.1.4.1.24552.500.1.1.2.0
NAME 'ldapPublicKey'
SUP top AUXILIARY
DESC 'MANDATORY: OpenSSH LPK objectclass'
MUST ( sshPublicKey $ uid ))
''
];
};
"cn={1}nextcloud,cn=schema".attrs = {
cn = "{1}nextcloud";
objectClass = "olcSchemaConfig";
olcAttributeTypes = [
''
(1.3.6.1.4.1.39430.1.1.1
NAME 'ownCloudQuota'
DESC 'User Quota (e.g. 15 GB)'
SYNTAX '1.3.6.1.4.1.1466.115.121.1.15')
''
];
olcObjectClasses = [
''
(1.3.6.1.4.1.39430.1.2.1
NAME 'ownCloud'
DESC 'ownCloud LDAP Schema'
AUXILIARY
MUST ( mail $ userPassword )
MAY ( ownCloudQuota ))
''
];
};
"cn={1}gogs,cn=schema".attrs = {
cn = "{1}gogs";
objectClass = "olcSchemaConfig";
olcObjectClasses = [
''
( 1.3.6.1.4.1.28293.1.2.4 NAME 'gitlab'
SUP uidObject AUXILIARY
DESC 'Added to an account to allow gitlab access'
MUST (mail))
''
];
};
"cn={1}homeAssistant,cn=schema".attrs = {
cn = "{1}homeAssistant";
objectClass = "olcSchemaConfig";
olcObjectClasses = [
''
(1.3.6.1.4.1.28297.1.2.4 NAME 'homeAssistant'
SUP uidObject AUXILIARY
DESC 'Added to an account to allow home-assistant access'
MUST (mail) )
''
];
};
# "cn={1}ttrss,cn=schema".attrs = {
# cn = "{1}ttrss";
# objectClass = "olcSchemaConfig";
# olcObjectClasses = [
# ''
# ( 1.3.6.1.4.1.28294.1.2.4 NAME 'ttrss'
# SUP top AUXILIARY
# DESC 'Added to an account to allow tinytinyrss access'
# MUST ( mail $ userPassword ))
# ''
# ];
# };
# "cn={1}prometheus,cn=schema".attrs = {
# cn = "{1}prometheus";
# objectClass = "olcSchemaConfig";
# olcObjectClasses = [
# ''
# ( 1.3.6.1.4.1.28296.1.2.4
# NAME 'prometheus'
# SUP uidObject AUXILIARY
# DESC 'Added to an account to allow prometheus access'
# MUST (mail))
# ''
# ];
# };
# "cn={1}loki,cn=schema".attrs = {
# cn = "{1}loki";
# objectClass = "olcSchemaConfig";
# olcObjectClasses = [
# ''
# ( 1.3.6.1.4.1.28299.1.2.4
# NAME 'loki'
# SUP uidObject AUXILIARY
# DESC 'Added to an account to allow loki access'
# MUST (mail))
# ''
# ];
# };
# "cn={1}flood,cn=schema".attrs = {
# cn = "{1}flood";
# objectClass = "olcSchemaConfig";
# olcObjectClasses = [
# ''
# (1.3.6.1.4.1.28300.1.2.4 NAME 'flood'
# SUP uidObject AUXILIARY
# DESC 'Added to an account to allow flood access'
# MUST (mail))
# ''
# ];
# };
};
};
/* ensure openldap is launched after certificates are created */
systemd.services.openldap = {
wants = [ "acme-${domain}.service" ];
after = [ "acme-${domain}.service" ];
};
users.groups.acme.members = [ "openldap" ];
/* trigger the actual certificate generation for your hostname */
security.acme.certs."ldap.${domain}" = {
extraDomainNames = [
"ldap-test.${domain}"
"ldap-02.${domain}"
];
postRun = "systemctl restart openldap.service";
};
sops.secrets.openldap-rootpw = {
owner = "openldap";
sopsFile = ./secrets.yaml;
};
networking.firewall.allowedTCPPorts = [ 389 636 ];
}

View File

@@ -0,0 +1,40 @@
openldap-rootpw: ENC[AES256_GCM,data:cwxXkLd5jyrGI4IoewQL4HBH/Xi6SfVkizitKa0Qyr30SeH02VDLyGXzbpuRfZuPRePbLFEOMKUcCwkZV/2RTA==,iv:SqxCK5rnsgU6i68ZoBZtbRxLgUe59wMg7EYO5jlAwFw=,tag:SbORanDDqLPF+dNleqzYNg==,type:str]
dovecot-ldap-password: ENC[AES256_GCM,data:czkIYqmWXs0U6LFbetJg47VQHw+E5kcHpdwRGmZAKi6Q3hP/IOW8N1dhIkrQRLzKih9vWXKhyo6BDA2e6w6pHQ==,iv:Ka18K0/pGJubfiQ1GKq3uxwZ/CgujO1DulwBomXBbco=,tag:QACpoLJ+QIMM3mCI/vTjtA==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age16veg3fmvpfm7a89a9fc8dvvsxmsthlm70nfxqspr6t8vnf9wkcwsvdq38d
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpcnBXWk1JaE9McXlzMUk3
bXZHRXRQbDdsK25MNTY1MHA0UWhCcWJRdlVVCmlzcDE0L1ZOQzB6MVBPYUdncUZr
M0FGSkdxaFpiY2NUTlRBSUZZdUJmRzgKLS0tIGs3UlNwUDJYVTFHTXcvZkJCS0w5
cGJic1JZTHE2NnkxN2JuYXY0TmZUWjAKN6orRU5LnJbl84HtKy0MBNA/PiuEmuhO
JL/tpFX+LiOScFHrvb40Ka6YvnyER+rufZXi1xknBzW1uyDt+lSyQw==
-----END AGE ENCRYPTED FILE-----
- recipient: age1jyeppc8yl2twnv8fwcewutd5gjewnxl59lmhev6ygds9qel8zf8syt7zz4
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB1RWNUenVwS2xISmRQK3RN
ZEc5TGF2MGNocnZWNkhHQ1lGOU1adlFCZlJ3CmdOWC8vQVd4aEdLVTJtNTZCM1R5
VndOM3RJRy9laW1pa1k0TUt5UTEyVmsKLS0tIFB5aDNZQXlTRlYvUkJaOXI3NVky
VHFINVFjVVVsTXViTDV0QmFBWTRsbVkKJCjMI1GImwSKpgTDVwF5xAdnbUqBkxUO
vYFySQg5p12lZ7RtMbxdql24a52J9Jm/2dMMKKph339vw/rcW7YRXQ==
-----END AGE ENCRYPTED FILE-----
- recipient: age1azmxsw5llmp2nnsv3yc2l8paelmq9rfepxd8jvmswgsmax0qyyxqdnsc7t
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBDdXJGOFgzZFhFWXEvZkNB
OWI3MDQxVGZ5dGpXM245ZUlHZHJhRnR0UWl3CnNKeGhLNVdYVWdoWWFBaC90ZUhj
Mjc4MDQxa0ZaMnVaSndWRDFrTjVpZmMKLS0tIG9rZGJJb0J6SE1lSjdWSHc0V2FH
dGJqSzB5NE5ESzE1L0ZxTDBORnpvRUUKtKejHfzBGnrOJzPStRUcjD/cRq3BqsdP
PtSh9ujx/aazn1O86wMYuIgb1WfWL3ZyTtoPCukGKth9KT1JweU1eA==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2023-01-22T20:44:32Z"
mac: ENC[AES256_GCM,data:nKR47o4Evt4TPyndEwZlnP/ctGaaz6wwn0k+JnDCL3FW1TO64spNL7xDcoxWwPuRLrgjgtazsm4Tevplzc3J/N4dhnPAdiPtZOQd3tKibIJKDkxG+6upGvzMMrXXInzoGVqwFMrZmdIqlpLAgqX/1VwY4Tnrf0IfiwJ8wWmSZe8=,iv:FUL/gcDZBZrclYupzstSFG86NOnEOvvgr8ou7wVQ3AY=,tag:KPXm0HHwc8v64dnqGqlFUQ==,type:str]
pgp: []
unencrypted_suffix: _unencrypted
version: 3.7.3

View File

@@ -0,0 +1,2 @@
#(import <nixpkgs> {}).callPackage (builtins.fetchurl "https://raw.githubusercontent.com/delroth/infra.delroth.net/master/pkgs/parsec.nix") {}
#(import <nixpkgs> {}).callPackage (builtins.fetchurl "https://raw.githubusercontent.com/delroth/infra.delroth.net/38a040e4bbfef7ee13c4b0a75dc79c77ddfdc759/pkgs/parsec.nix") {}

View File

@@ -0,0 +1,42 @@
{ config, pkgs, ... }:
{
sops.secrets.plausible-admin-password = {
sopsFile = ./secrets.yaml;
};
sops.secrets.plausible-secret = {
sopsFile = ./secrets.yaml;
};
services.plausible = {
enable = true;
releaseCookiePath = "/run/secrets/plausible-release-cookie";
server = {
secretKeybaseFile = config.sops.secrets.plausible-secret.path;
baseUrl = "plausible.cloonar.com";
};
database.clickhouse = {
setup = false;
};
database.postgres = {
dbname = "plausible";
};
adminUser = {
activate = true;
email = "plausible@cloonar.com";
passwordFile = config.sops.secrets.plausible-admin-password.path;
};
};
services.nginx.enable = true;
services.nginx.virtualHosts."plausible.cloonar.com" = {
forceSSL = true;
enableACME = true;
acmeRoot = null;
locations."/" = {
proxyPass = "http://localhost:8000";
};
};
services.postgresqlBackup.databases = [ "plausible" ];
}

View File

@@ -0,0 +1,31 @@
plausible-admin-password: ENC[AES256_GCM,data:S46PqHBpGRnAq5lv9Pb0NZalg+9hVj8330prH45DlhGG679W4gXzQOwetgQlsr3HAL8I3Uu6Dk4/FWHIKQKGFw==,iv:x9AXhJEoe0Ilx/WEc8f3g2hS0ZFo/DvKcwQBk62KUSk=,tag:Pn9lddsKeEKIv2IDQjFHjA==,type:str]
plausible-secret: ENC[AES256_GCM,data:HS5rDCbGzjhgv5lxNS9IgjrQ+EzBQgfVQxjHK3Kh++9Q1o7TO5IsFBaBw9BRXKMvzILpmCV737ltBcTV6zSZaZBiJmCf+hA7501Zn1RxWMjujlMWf+DTM/Ut7VK/4oc63K/4TLvvGhManl0ia0f8Rwm720JBdbnIrb7e6YSfe74=,iv:atUmTo4+uHFMIiWoxDe79Pj4dE00+0o30DsqUPoL9NY=,tag:jFd+xZvwyRB//Nx1e8BgKw==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age16veg3fmvpfm7a89a9fc8dvvsxmsthlm70nfxqspr6t8vnf9wkcwsvdq38d
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBMUGdqU1hWR1dMVXhnYUNY
ZFYvb3pCOG1DMWs5bGtscmNTdHdQY0ltRGhrCmhJZkdWMmNUMk5WaFI0REZNc0Jp
SkFHUzB0RjA5cWh2bDd6NDAxeUFqd1kKLS0tIEFmcFNOUTBPcXpJUDRLRGRVWWVC
b3FkcVRhLzNZa0poUmdPM3NoRnRnUnMK/HRww0ewz+LIBeWlbgHlgh/+mAJuhmwI
xEID8pv0gOh+HGEKdRa+BajLmDhTfvnoJadhVswSTMWc/iewgAPs3w==
-----END AGE ENCRYPTED FILE-----
- recipient: age1y6lvl5jkwc47p5ae9yz9j9kuwhy7rtttua5xhygrgmr7ehd49svsszyt42
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBKcXdNeTFzeWJkSUx4TllE
azZtQXhtSmZHWk1ld05hcTNXTWhaQlY4eVFFClZrOTN5QTRHKzR1VFhENFEyQ3Qv
Yno4aUw3Y2c5Q1RUSTF0L1hBdTY5bE0KLS0tIHd0T2Zpeko5QVFnVHZ4alh5cUJz
ejh0bExnMEF1MUhLZ0N5SzFpZnM0cFUKgzlIYqwgi9nsik59S+Bxj/Mj5f09JS5I
nVG+2A05+8XxA+eGqcRTYFyo3w71R8hdfmu3vfXl+Ywb+P51K+SDkA==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2023-04-17T20:01:37Z"
mac: ENC[AES256_GCM,data:cm8XdC5CUaQ5KUrkKXSaEfJ/VbxpQQAJg5LZ5O5+3kkWL1qSAK3z4PSnGs58GFlEfw2iFcyOWQr/wH7GPZsSo8/J4fN0zTqYkn+Mn2GZaYMcqr1K1U8tIgq6qLGf2UqG5/AvED4nGY48/aGvGyYyKIak8sgPm8uGz+DwX+ApiMk=,iv:jNe/PmLq5fHuyxQQj4H4hF8V7rCl/Xj3wNMP2bEsEQY=,tag:BNOi0YVH83S4pV9e38WcHQ==,type:str]
pgp: []
unencrypted_suffix: _unencrypted
version: 3.7.3

246
utils/modules/postfix.nix Normal file
View File

@@ -0,0 +1,246 @@
{ pkgs
, lib
, config
, ...
}:
let
domain = config.networking.domain;
ldapServer = "ldap.cloonar.com";
# domain = "cloonar.com";
domains = pkgs.writeText "domains.cf" ''
server_host = ldap://${ldapServer}
search_base = ou=domains,dc=cloonar,dc=com
version = 3
bind = yes
start_tls = yes
bind_dn = cn=vmail,ou=system,ou=users,dc=cloonar,dc=com
bind_pw = @ldap-password@
scope = one
query_filter = (&(dc=%s)(objectClass=mailDomain))
result_attribute = postfixTransport
debuglevel = 0
'';
mailboxes = pkgs.writeText "mailboxes.cf" ''
server_host = ldap://${ldapServer}
search_base = ou=users,dc=%2,dc=%1
version = 3
bind = yes
start_tls = yes
bind_dn = cn=vmail,ou=system,ou=users,dc=cloonar,dc=com
bind_pw = @ldap-password@
scope = sub
query_filter = (&(uid=%u)(objectClass=mailAccount))
result_attribute = mail
debuglevel = 0
'';
senderLoginMaps = pkgs.writeText "sender_login_maps.cf" ''
server_host = ldap://${ldapServer}
search_base = dc=%2,dc=%1
version = 3
bind = yes
start_tls = yes
bind_dn = cn=vmail,ou=system,ou=users,dc=cloonar,dc=com
bind_pw = @ldap-password@
scope = sub
query_filter = (|(&(objectClass=mailAccount)(uid=%u))(&(objectClass=mailAlias)(mail=%s)))
result_attribute = maildrop, mail
debuglevel = 0
'';
accountsmap = pkgs.writeText "accountsmap.cf" ''
server_host = ldap://${ldapServer}
search_base = ou=users,dc=%2,dc=%1
version = 3
bind = yes
start_tls = yes
bind_dn = cn=vmail,ou=system,ou=users,dc=cloonar,dc=com
bind_pw = @ldap-password@
scope = sub
query_filter = (&(objectClass=mailAccount)(uid=%u))
result_attribute = mail
debuglevel = 0
'';
aliases = pkgs.writeText "aliases.cf" ''
server_host = ldap://${ldapServer}
search_base = ou=aliases,dc=%2,dc=%1
version = 3
bind = yes
start_tls = yes
bind_dn = cn=vmail,ou=system,ou=users,dc=cloonar,dc=com
bind_pw = @ldap-password@
scope = one
query_filter = (&(objectClass=mailAlias)(mail=%s))
result_attribute = maildrop
debuglevel = 0
'';
helo_access = pkgs.writeText "helo_access" ''
/^([0-9\.]+)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server sent non RFC compliant HELO identity (''${1})
cloonar.com REJECT ACCESS DENIED. Your email was rejected because the sending mail server sent non RFC compliant HELO identity (''${1})
ghetto.at REJECT ACCESS DENIED. Your email was rejected because the sending mail server sent non RFC compliant HELO identity (''${1})
'';
in
{
services.postfix = {
enable = true;
enableSubmission = true;
hostname = "mail.${domain}";
domain = "cloonar.com";
masterConfig."465" = {
type = "inet";
private = false;
command = "smtpd";
args = [
"-o smtpd_client_restrictions=permit_sasl_authenticated,reject"
"-o syslog_name=postfix/smtps"
"-o smtpd_tls_wrappermode=yes"
"-o smtpd_sasl_auth_enable=yes"
"-o smtpd_tls_security_level=none"
"-o smtpd_reject_unlisted_recipient=no"
"-o smtpd_recipient_restrictions="
"-o smtpd_relay_restrictions=permit_sasl_authenticated,reject"
"-o milter_macro_daemon_name=ORIGINATING"
];
};
mapFiles."helo_access" = helo_access;
config = {
# debug_peer_list = "10.42.96.190";
# smtp_bind_address = config.networking.eve.ipv4.address;
# smtp_bind_address6 = "2a01:4f9:2b:1605::1";
mailbox_transport = "lmtp:unix:private/dovecot-lmtp";
virtual_mailbox_domains = "ldap:/run/postfix/domains.cf";
virtual_mailbox_maps = "ldap:/run/postfix/mailboxes.cf";
virtual_alias_maps = "ldap:/run/postfix/accountsmap.cf,ldap:/run/postfix/aliases.cf";
virtual_transport = "lmtp:unix:private/dovecot-lmtp";
smtpd_sender_login_maps = "ldap:/run/postfix/sender_login_maps.cf";
# Do not display the name of the recipient table in the "User unknown" responses.
# The extra detail makes trouble shooting easier but also reveals information
# that is nobody elses business.
show_user_unknown_table_name = "no";
compatibility_level = "2";
# bigger attachement size
mailbox_size_limit = "202400000";
message_size_limit = "51200000";
smtpd_helo_required = "yes";
smtpd_delay_reject = "yes";
strict_rfc821_envelopes = "yes";
# send Limit
smtpd_error_sleep_time = "1s";
smtpd_soft_error_limit = "10";
smtpd_hard_error_limit = "20";
smtpd_use_tls = "yes";
smtp_tls_note_starttls_offer = "yes";
smtpd_tls_security_level = "may";
smtpd_tls_auth_only = "yes";
smtp_dns_support_level = "dnssec";
smtp_tls_security_level = "dane";
smtpd_tls_cert_file = "/var/lib/acme/mail.cloonar.com/full.pem";
smtpd_tls_key_file = "/var/lib/acme/mail.cloonar.com/key.pem";
smtpd_tls_CAfile = "/var/lib/acme/mail.cloonar.com/fullchain.pem";
smtpd_tls_dh512_param_file = config.security.dhparams.params.postfix512.path;
smtpd_tls_dh1024_param_file = config.security.dhparams.params.postfix2048.path;
smtpd_tls_session_cache_database = ''btree:''${data_directory}/smtpd_scache'';
smtpd_tls_mandatory_protocols = "!SSLv2,!SSLv3,!TLSv1,!TLSv1.1";
smtpd_tls_protocols = "!SSLv2,!SSLv3,!TLSv1,!TLSv1.1";
smtpd_tls_mandatory_ciphers = "medium";
tls_medium_cipherlist = "AES128+EECDH:AES128+EDH";
# authentication
smtpd_sasl_auth_enable = "yes";
smtpd_sasl_local_domain = "$mydomain";
smtpd_sasl_security_options = "noanonymous";
smtpd_sasl_tls_security_options = "$smtpd_sasl_security_options";
smtpd_sasl_type = "dovecot";
smtpd_sasl_path = "/var/lib/postfix/queue/private/auth";
smtpd_relay_restrictions = "
permit_mynetworks,
permit_sasl_authenticated,
defer_unauth_destination";
smtpd_client_restrictions = "
permit_mynetworks,
permit_sasl_authenticated,
reject_invalid_hostname,
reject_unknown_client,
permit";
smtpd_helo_restrictions = "
permit_mynetworks,
permit_sasl_authenticated,
reject_unauth_pipelining,
reject_non_fqdn_hostname,
reject_invalid_hostname,
warn_if_reject reject_unknown_hostname,
permit";
smtpd_recipient_restrictions = "
permit_mynetworks,
permit_sasl_authenticated,
reject_non_fqdn_sender,
reject_non_fqdn_recipient,
reject_non_fqdn_hostname,
reject_invalid_hostname,
reject_unknown_sender_domain,
reject_unknown_recipient_domain,
reject_unknown_client_hostname,
reject_unauth_pipelining,
reject_unknown_client,
permit";
smtpd_sender_restrictions = "
reject_non_fqdn_sender,
reject_unlisted_sender,
reject_authenticated_sender_login_mismatch,
permit_mynetworks,
permit_sasl_authenticated,
reject_unknown_sender_domain,
reject_unknown_client_hostname,
reject_unknown_address";
smtpd_etrn_restrictions = "permit_mynetworks, reject";
smtpd_data_restrictions = "reject_unauth_pipelining, reject_multi_recipient_bounce, permit";
};
};
systemd.tmpfiles.rules = [ "d /run/postfix 0750 postfix postfix -" ];
systemd.services.postfix.preStart = ''
sed -e "s/@ldap-password@/$(cat ${config.sops.secrets.dovecot-ldap-password.path})/" ${domains} > /run/postfix/domains.cf
sed -e "s/@ldap-password@/$(cat ${config.sops.secrets.dovecot-ldap-password.path})/" ${mailboxes} > /run/postfix/mailboxes.cf
sed -e "s/@ldap-password@/$(cat ${config.sops.secrets.dovecot-ldap-password.path})/" ${accountsmap} > /run/postfix/accountsmap.cf
sed -e "s/@ldap-password@/$(cat ${config.sops.secrets.dovecot-ldap-password.path})/" ${aliases} > /run/postfix/aliases.cf
sed -e "s/@ldap-password@/$(cat ${config.sops.secrets.dovecot-ldap-password.path})/" ${senderLoginMaps} > /run/postfix/sender_login_maps.cf
'';
security.dhparams = {
enable = true;
params.postfix512.bits = 512;
params.postfix2048.bits = 1024;
};
security.acme.certs."mail.${domain}" = {
extraDomainNames = [
"mail-test.${domain}"
"mail-02.${domain}"
];
postRun = "systemctl restart postfix.service";
group = "postfix";
};
networking.firewall.allowedTCPPorts = [
25 # smtp
465 # smtps
587 # submission
];
}

View File

@@ -0,0 +1,112 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.room-assistant;
in
{
options = {
services.room-assistant = {
enable = mkEnableOption (lib.mdDoc "room-assistant");
name = mkOption {
type = with types; uniq string;
description = "
...
";
default = "room";
};
mqttHost = mkOption {
type = with types; uniq string;
description = "
...
";
default = "";
};
mqttUser = mkOption {
type = with types; uniq string;
description = "
...
";
default = "espresense";
};
mqttPassword = mkOption {
type = with types; uniq string;
description = "
...
";
default = "insecure-password";
};
};
};
config = mkIf cfg.enable {
hardware = {
bluetooth.enable = true;
deviceTree.filter = "bcm2711-rpi-*.dtb";
};
systemd.services = {
btattach = {
before = [ "bluetooth.service" ];
after = [ "dev-ttyAMA0.device" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart = "${pkgs.bluez}/bin/btattach -B /dev/ttyAMA0 -P bcm -S 3000000";
};
};
};
virtualisation.docker.enable = true;
environment.etc."room-assistant.yml" = {
text = ''
global:
instanceName: ${cfg.name}
integrations:
- homeAssistant
- bluetoothClassic
homeAssistant:
mqttUrl: 'mqtt://${cfg.mqttHost}'
mqttOptions:
username: ${cfg.mqttUser}
password: ${cfg.mqttPassword}
bluetoothClassic:
addresses:
- A8:5B:B7:98:84:F0
- 00:24:E4:E6:FE:AD
'';
# The UNIX file mode bits
mode = "0440";
};
systemd.services."room-assistant" = {
description = "room-assistant";
wantedBy = [ "multi-user.target" ];
after = [ "docker.service" "docker.socket" ];
requires = [ "docker.service" "docker.socket" ];
script = ''
exec ${pkgs.docker}/bin/docker run \
--rm \
--name=room-assistant \
--network=host \
-v /var/run/dbus:/var/run/dbus \
-v /etc/room-assistant.yml:/room-assistant/config/local.yml \
--cap-add=NET_ADMIN \
mkerix/room-assistant:2.20.0
'';
preStop = "${pkgs.docker}/bin/docker stop room-assistant";
reload = "${pkgs.docker}/bin/docker restart room-assistant";
serviceConfig = {
ExecStartPre = "-${pkgs.docker}/bin/docker rm -f room-assistant";
ExecStopPost = "-${pkgs.docker}/bin/docker rm -f room-assistant";
TimeoutStartSec = 0;
TimeoutStopSec = 120;
Restart = "always";
};
};
};
}

View File

@@ -0,0 +1,22 @@
{ pkgs, ... }:
let
domain = "mail-test.cloonar.com";
in
{
services.roundcube = {
enable = true;
hostName = "${domain}";
extraConfig = ''
$config['imap_host'] = 'tls://imap-test.cloonar.com';
$config['smtp_server'] = "tls://mail-test.cloonar.com";
$config['smtp_user'] = "%u";
$config['smtp_pass'] = "%p";
'';
};
services.nginx.virtualHosts."${domain}" = {
forceSSL = true;
enableACME = true;
acmeRoot = null;
};
}

131
utils/modules/rspamd.nix Normal file
View File

@@ -0,0 +1,131 @@
{ pkgs
, config
, ...
}:
let
domain = config.networking.domain;
localConfig = pkgs.writeText "local.conf" ''
logging {
level = "notice";
}
classifier "bayes" {
autolearn = true;
}
dkim_signing {
path = "/var/lib/rspamd/dkim/$domain.$selector.key";
selector = "default";
allow_username_mismatch = true;
}
arc {
path = "/var/lib/rspamd/dkim/$domain.$selector.key";
selector = "default";
allow_username_mismatch = true;
}
milter_headers {
use = ["authentication-results", "x-spam-status"];
authenticated_headers = ["authentication-results"];
}
replies {
action = "no action";
}
url_reputation {
enabled = true;
}
phishing {
openphish_enabled = true;
# too much memory
#phishtank_enabled = true;
}
neural {
enabled = true;
}
neural_group {
symbols = {
"NEURAL_SPAM" {
weight = 3.0; # sample weight
description = "Neural network spam";
}
"NEURAL_HAM" {
weight = -3.0; # sample weight
description = "Neural network ham";
}
}
}
'';
sieve-spam-filter = pkgs.callPackage ../pkgs/sieve-spam-filter { };
in
{
services.rspamd = {
enable = true;
extraConfig = ''
.include(priority=1,duplicate=merge) "${localConfig}"
'';
postfix.enable = true;
workers.controller = {
extraConfig = ''
count = 1;
static_dir = "''${WWWDIR}";
password = "$2$7rb4gnnw8qbcy3x3m7au8c4mezecfjim$da4ahtt3gnjtbj7ni6bt1q8jwgqtzxp5ck6941m6prjxsz3udfgb";
enable_password = "$2$xo1qdd1zgozwto8yazr1o35zbarbzcgp$u8mx6hcsb1qdscejb4zadcb3iucmm4mw6btgmim9h6e5d8cpy5ib";
'';
};
};
services.dovecot2 = {
mailboxes.Spam = {
auto = "subscribe";
specialUse = "Junk";
};
extraConfig = ''
protocol imap {
mail_plugins = $mail_plugins imap_sieve
}
plugin {
sieve_plugins = sieve_imapsieve sieve_extprograms
# From elsewhere to Spam folder
imapsieve_mailbox1_name = Spam
imapsieve_mailbox1_causes = COPY
imapsieve_mailbox1_before = file:/var/lib/dovecot/sieve/report-spam.sieve
# From Spam folder to elsewhere
imapsieve_mailbox2_name = *
imapsieve_mailbox2_from = Spam
imapsieve_mailbox2_causes = COPY
imapsieve_mailbox2_before = file:/var/lib/dovecot/sieve/report-ham.sieve
# Move Spam emails to Spam folder
sieve_before = /var/lib/dovecot/sieve/move-to-spam.sieve
sieve_pipe_bin_dir = ${sieve-spam-filter}/bin
sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment
}
'';
};
services.nginx.enable = true;
services.nginx.virtualHosts."rspamd.${domain}" = {
forceSSL = true;
enableACME = true;
acmeRoot = null;
locations."/".extraConfig = ''
proxy_pass http://localhost:11334;
'';
};
# systemd.services.rspamd.serviceConfig.SupplementaryGroups = [ "redis-rspamd" ];
systemd.services.dovecot2.preStart = ''
mkdir -p /var/lib/dovecot/sieve/
for i in ${sieve-spam-filter}/share/sieve-rspamd-filter/*.sieve; do
dest="/var/lib/dovecot/sieve/$(basename $i)"
cp "$i" "$dest"
${pkgs.dovecot_pigeonhole}/bin/sievec "$dest"
done
chown -R "${config.services.dovecot2.mailUser}:${config.services.dovecot2.mailGroup}" /var/lib/dovecot/sieve
'';
}

View File

@@ -0,0 +1,118 @@
{ pkgs, lib, config, ... }:
let
domain = "self-service.cloonar.com";
php = pkgs.php82;
version = "1.5.2";
dataDir = "/var/www/${domain}";
in {
environment.systemPackages = with pkgs; [
smarty3
];
systemd.services."phpfpm-${domain}".serviceConfig.ProtectHome = lib.mkForce false;
systemd.services.selfservicepassword_setup = let
overrideConfig = pkgs.writeText "nextcloud-config.php" ''
<?php
$ldap_url = "ldap://ldap-test.cloonar.com:389";
$ldap_starttls = true;
define("SMARTY", "Smarty.class.php");
$use_tokens = false;
$use_sms = false;
'';
in {
wantedBy = [ "multi-user.target" ];
before = [ "phpfpm-${domain}.service" ];
script = ''
mkdir -p ${dataDir}/public
curl -L https://github.com/ltb-project/self-service-password/archive/refs/tags/v${version}.tar.gz > ${dataDir}/package.tar.gz
/run/current-system/sw/bin/tar xf ${dataDir}/package.tar.gz -C ${dataDir}
mv ${dataDir}/self-service-password-${version}/* ${dataDir}/public/
rm -rf ${dataDir}/self-service-password-${version}
cp ${overrideConfig} ${dataDir}/public/conf/config.inc.local.php
'';
path = [ pkgs.gzip pkgs.curl ];
serviceConfig.Type = "oneshot";
serviceConfig.User = domain;
};
services.phpfpm.pools."${domain}" = {
user = domain;
settings = {
"listen.owner" = config.services.nginx.user;
"pm" = "dynamic";
"pm.max_children" = 32;
"pm.max_requests" = 500;
"pm.start_servers" = 2;
"pm.min_spare_servers" = 2;
"pm.max_spare_servers" = 5;
"php_flag[display_errors]" = "on";
"php_admin_value[error_log]" = "/var/log/${domain}.error.log";
"php_admin_flag[log_errors]" = "on";
"php_value[include_path]" = ".:/usr/share/php:${pkgs.smarty3}";
"catch_workers_output" = "yes";
"access.log" = "/var/log/$pool.access.log";
};
phpPackage = php;
phpEnv."PATH" = lib.makeBinPath [ php ];
};
services.nginx.virtualHosts."${domain}" = {
forceSSL = true;
enableACME = true;
acmeRoot = null;
root = "${dataDir}/public/htdocs";
locations."/favicon.ico".extraConfig = ''
log_not_found off;
access_log off;
'';
# extraConfig = ''
# if (!-e $request_filename) {
# rewrite ^/(.+)\.(\d+)\.(php|js|css|png|jpg|gif|gzip)$ /$1.$3 last;
# }
# '';
locations."/".extraConfig = ''
index index.php index.html;
try_files $uri $uri/ /index.php$is_args$args;
'';
locations."~ [^/]\.php(/|$)".extraConfig = ''
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
if (!-f $document_root$fastcgi_script_name) {
return 404;
}
include ${pkgs.nginx}/conf/fastcgi_params;
include ${pkgs.nginx}/conf/fastcgi.conf;
fastcgi_buffer_size 32k;
fastcgi_buffers 8 16k;
fastcgi_connect_timeout 240s;
fastcgi_read_timeout 240s;
fastcgi_send_timeout 240s;
fastcgi_pass unix:${config.services.phpfpm.pools."${domain}".socket};
fastcgi_index index.php;
'';
# locations."~ /\.".extraConfig = ''
# log_not_found off;
# deny all;
# '';
#
# locations."~ /scripts".extraConfig = ''
# log_not_found off;
# deny all;
# '';
};
users.users."${domain}" = {
#isSystemUser = true;
isNormalUser = true;
createHome = true;
home = dataDir;
homeMode= "770";
group = "nginx";
};
users.groups.${domain} = {};
}

View File

@@ -0,0 +1,319 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.webstack;
instanceOpts = { name, ... }:
{
options = {
user = mkOption {
type = types.nullOr types.str;
default = null;
description = lib.mdDoc ''
User of the typo3 instance. Defaults to attribute name in instances.
'';
example = "example.org";
};
domain = mkOption {
type = types.nullOr types.str;
default = null;
description = lib.mdDoc ''
Domain of the typo3 instance. Defaults to attribute name in instances.
'';
example = "example.org";
};
domainAliases = mkOption {
type = types.listOf types.str;
default = [];
example = [ "www.example.org" "example.org" ];
description = lib.mdDoc ''
Additional domains served by this typo3 instance.
'';
};
phpPackage = mkOption {
type = types.package;
example = literalExpression "pkgs.php";
description = lib.mdDoc ''
Which PHP package to use in this typo3 instance.
'';
};
enableMysql = mkEnableOption (lib.mdDoc "MySQL Database");
enableDefaultLocations = mkEnableOption (lib.mdDoc "Create default nginx location directives") // { default = true; };
authorizedKeys = mkOption {
type = types.listOf types.str;
default = null;
description = lib.mdDoc ''
Authorized keys for the typo3 instance ssh user.
'';
};
extraConfig = mkOption {
type = types.lines;
default = ''
if (!-e $request_filename) {
rewrite ^/(.+)\.(\d+)\.(php|js|css|png|jpg|gif|gzip)$ /$1.$3 last;
}
'';
description = lib.mdDoc ''
These lines go to the end of the vhost verbatim.
'';
};
locations = mkOption {
type = types.attrsOf (types.submodule (import <nixpkgs/nixos/modules/services/web-servers/nginx/location-options.nix> {
inherit lib config;
}));
default = {};
example = literalExpression ''
{
"/" = {
proxyPass = "http://localhost:3000";
};
};
'';
description = lib.mdDoc "Declarative location config";
};
};
};
in
{
options.services.webstack = {
dataDir = mkOption {
type = types.path;
default = "/var/www";
description = lib.mdDoc ''
The data directory for MySQL.
::: {.note}
If left as the default value of `/var/www` this directory will automatically be created before the web
server starts, otherwise you are responsible for ensuring the directory exists with appropriate ownership and permissions.
:::
'';
};
instances = mkOption {
type = types.attrsOf (types.submodule instanceOpts);
default = {};
description = lib.mdDoc "Create vhosts for typo3";
example = literalExpression ''
{
"typo3.example.com" = {
domain = "example.com";
domainAliases = [ "www.example.com" ];
phpPackage = pkgs.php81;
authorizedKeys = [
"ssh-rsa AZA=="
];
};
};
'';
};
};
config = {
systemd.services = mapAttrs' (instance: instanceOpts:
let
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
in
nameValuePair "phpfpm-${domain}" {
serviceConfig = {
ProtectHome = lib.mkForce "tmpfs";
BindPaths = "BindPaths=/var/www/${domain}:/var/www/${domain}";
};
}
) cfg.instances;
services.phpfpm.pools = mapAttrs' (instance: instanceOpts:
let
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
user = if instanceOpts.user != null
then instanceOps.user
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
in
nameValuePair domain {
user = user;
settings = {
"listen.owner" = config.services.nginx.user;
"pm" = "dynamic";
"pm.max_children" = 32;
"pm.max_requests" = 500;
"pm.start_servers" = 2;
"pm.min_spare_servers" = 2;
"pm.max_spare_servers" = 5;
"php_admin_value[error_log]" = "syslog";
"php_admin_value[max_execution_time]" = 240;
"php_admin_value[max_input_vars]" = 1500;
"access.log" = "/var/log/$pool.access.log";
};
phpPackage = instanceOpts.phpPackage;
phpEnv."PATH" = pkgs.lib.makeBinPath [ instanceOpts.phpPackage ];
}
) cfg.instances;
};
config.services.nginx.virtualHosts = mapAttrs' (instance: instanceOpts:
let
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
user = if instanceOpts.user != null
then instanceOps.user
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
in
nameValuePair domain {
forceSSL = true;
enableACME = true;
acmeRoot = null;
root = cfg.dataDir + "/" + domain + "/public";
locations = lib.mkMerge [
instanceOpts.locations
(mkIf instanceOpts.enableDefaultLocations {
"/favicon.ico".extraConfig = ''
log_not_found off;
access_log off;
'';
# Cache.appcache, your document html and data
"~* \\.(?:manifest|appcache|html?|xml|json)$".extraConfig = ''
expires -1;
# access_log logs/static.log; # I don't usually include a static log
'';
"~* \\.(jpe?g|png)$".extraConfig = ''
set $red Z;
if ($http_accept ~* "webp") {
set $red A;
}
if (-f $document_root/webp/$request_uri.webp) {
set $red "''${red}B";
}
if ($red = "AB") {
add_header Vary Accept;
rewrite ^ /webp/$request_uri.webp;
}
'';
# Cache Media: images, icons, video, audio, HTC
"~* \\.(?:jpg|jpeg|gif|png|webp|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc|woff2)$".extraConfig = ''
expires 1y;
access_log off;
add_header Cache-Control "public";
'';
# Feed
"~* \\.(?:rss|atom)$".extraConfig = ''
expires 1h;
add_header Cache-Control "public";
'';
# Cache CSS, Javascript, Images, Icons, Video, Audio, HTC, Fonts
"~* \\.(?:css|js|jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc|woff2)$".extraConfig = ''
expires 1y;
access_log off;
add_header Cache-Control "public";
'';
"/".extraConfig = ''
index index.php index.html;
try_files $uri $uri/ /index.php$is_args$args;
'';
})
{
"~ [^/]\\.php(/|$)".extraConfig = ''
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
if (!-f $document_root$fastcgi_script_name) {
return 404;
}
include ${pkgs.nginx}/conf/fastcgi_params;
include ${pkgs.nginx}/conf/fastcgi.conf;
fastcgi_buffer_size 32k;
fastcgi_buffers 8 16k;
fastcgi_connect_timeout 240s;
fastcgi_read_timeout 240s;
fastcgi_send_timeout 240s;
fastcgi_pass unix:${config.services.phpfpm.pools."${domain}".socket};
fastcgi_index index.php;
'';
}
];
extraConfig = instanceOpts.extraConfig;
# locations = mapAttrs' (location: locationOpts:
# nameValuePair location locationOpts) instanceOpts.locations;
}
) cfg.instances;
config.users.users = mapAttrs' (instance: instanceOpts:
let
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
user = if instanceOpts.user != null
then instanceOps.user
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
in
nameValuePair user {
isNormalUser = true;
createHome = true;
home = "/var/www/" + domain;
homeMode= "770";
group = config.services.nginx.group;
openssh.authorizedKeys.keys = instanceOpts.authorizedKeys;
}
) cfg.instances;
config.users.groups = mapAttrs' (instance: instanceOpts:
let
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
user = if instanceOpts.user != null
then instanceOps.user
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
in nameValuePair user {}) cfg.instances;
config.services.mysql.ensureUsers = mapAttrsToList (instance: instanceOpts:
let
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
user = if instanceOpts.user != null
then instanceOps.user
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
in
mkIf instanceOpts.enableMysql {
name = user;
ensurePermissions = {
"${user}.*" = "ALL PRIVILEGES";
};
}) cfg.instances;
config.services.mysql.ensureDatabases = mapAttrsToList (instance: instanceOpts:
let
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
user = if instanceOpts.user != null
then instanceOps.user
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
in
mkIf instanceOpts.enableMysql user
) cfg.instances;
config.services.mysqlBackup.databases = mapAttrsToList (instance: instanceOpts:
let
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
user = if instanceOpts.user != null
then instanceOps.user
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
in
mkIf instanceOpts.enableMysql user
) cfg.instances;
}

View File

@@ -0,0 +1,367 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.typo3;
instanceOpts = { name, ... }:
{
options = {
user = mkOption {
type = types.nullOr types.str;
default = null;
description = lib.mdDoc ''
User of the typo3 instance. Defaults to attribute name in instances.
'';
example = "example.org";
};
domain = mkOption {
type = types.nullOr types.str;
default = null;
description = lib.mdDoc ''
Domain of the typo3 instance. Defaults to attribute name in instances.
'';
example = "example.org";
};
domainAliases = mkOption {
type = types.listOf types.str;
default = [];
example = [ "www.example.org" "example.org" ];
description = lib.mdDoc ''
Additional domains served by this typo3 instance.
'';
};
phpPackage = mkOption {
type = types.package;
example = literalExpression "pkgs.php";
description = lib.mdDoc ''
Which PHP package to use in this typo3 instance.
'';
};
authorizedKeys = mkOption {
type = types.listOf types.str;
default = null;
description = lib.mdDoc ''
Authorized keys for the typo3 instance ssh user.
'';
};
};
};
in
{
options.services.typo3 = {
dataDir = mkOption {
type = types.path;
default = "/var/www";
description = lib.mdDoc ''
The data directory for MySQL.
::: {.note}
If left as the default value of `/var/www` this directory will automatically be created before the web
server starts, otherwise you are responsible for ensuring the directory exists with appropriate ownership and permissions.
:::
'';
};
instances = mkOption {
type = types.attrsOf (types.submodule instanceOpts);
default = {};
description = lib.mdDoc "Create vhosts for typo3";
example = literalExpression ''
{
"typo3.example.com" = {
domain = "example.com";
domainAliases = [ "www.example.com" ];
phpPackage = pkgs.php82;
authorizedKeys = [
"ssh-rsa AZA=="
];
};
};
'';
};
};
config = {
# systemd.services = mapAttrs' (instance: instanceOpts:
# let
# domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
# in
# nameValuePair "phpfpm-${domain}" {
# serviceConfig = {
# ProtectHome = lib.mkForce "tmpfs";
# BindPaths = "BindPaths=/var/www/${domain}:/var/www/${domain}";
# };
# }
# ) cfg.instances;
systemd.timers = mapAttrs' (instance: instanceOpts:
let
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
user = if instanceOpts.user != null
then instanceOps.user
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
in
nameValuePair ("typo3-cron-" + domain) {
wantedBy = [ "timers.target" ];
timerConfig = {
OnCalendar = "05:00";
Unit = "typo3-cron-" + domain + ".service";
};
}
) cfg.instances;
systemd.services = mapAttrs' (instance: instanceOpts:
let
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
user = if instanceOpts.user != null
then instanceOps.user
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
in
nameValuePair ("typo3-cron-" + domain) {
script = ''
set -eu
${instanceOpts.phpPackage}/bin/php /var/www/${domain}/.Build/bin/typo3 scheduler:run
${instanceOpts.phpPackage}/bin/php /var/www/${domain}/.Build/bin/typo3 ke_search:indexing
'';
serviceConfig = {
Type = "oneshot";
User = user;
};
}
) cfg.instances;
services.phpfpm.pools = mapAttrs' (instance: instanceOpts:
let
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
user = if instanceOpts.user != null
then instanceOps.user
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
in
nameValuePair domain {
user = user;
settings = {
"listen.owner" = config.services.nginx.user;
"pm" = "dynamic";
"pm.max_children" = 32;
"pm.max_requests" = 500;
"pm.start_servers" = 2;
"pm.min_spare_servers" = 2;
"pm.max_spare_servers" = 5;
"php_admin_value[error_log]" = "syslog";
"php_admin_value[max_execution_time]" = 240;
"php_admin_value[max_input_vars]" = 1500;
"php_admin_value[upload_max_filesize]" = "256M";
"php_admin_value[post_max_size]" = "256M";
"access.log" = "/var/log/$pool.access.log";
};
phpOptions = ''
opcache.enable=1
opcache.memory_consumption=128
opcache.validate_timestamps=0
opcache.revalidate_path=0
'';
phpPackage = instanceOpts.phpPackage;
phpEnv."PATH" = pkgs.lib.makeBinPath [ instanceOpts.phpPackage ];
}
) cfg.instances;
};
config.services.nginx.virtualHosts = mapAttrs' (instance: instanceOpts:
let
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
user = if instanceOpts.user != null
then instanceOps.user
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
in
nameValuePair domain {
forceSSL = true;
enableACME = true;
acmeRoot = null;
root = cfg.dataDir + "/" + domain + "/public";
serverAliases = instanceOpts.domainAliases;
locations."/favicon.ico".extraConfig = ''
log_not_found off;
access_log off;
'';
extraConfig = ''
if (!-e $request_filename) {
rewrite ^/(.+)\.(\d+)\.(php|js|css|png|jpg|gif|gzip)$ /$1.$3 last;
}
'';
# TYPO3 - Block access to composer files
locations."~* composer\\.(?:json|lock)".extraConfig = ''
deny all;
'';
# TYPO3 - Block access to flexform files
locations."~* flexform[^.]*\\.xml".extraConfig = ''
deny all;
'';
# TYPO3 - Block access to language files
locations."~* locallang[^.]*\\.(?:xml|xlf)$".extraConfig = ''
deny all;
'';
# TYPO3 - Block access to static typoscript files
locations."~* ext_conf_template\\.txt|ext_typoscript_constants\\.txt|ext_typoscript_setup\\.txt".extraConfig = ''
deny all;
'';
# TYPO3 - Block access to miscellaneous protected files
locations."~* /.*\\.(?:bak|co?nf|cfg|ya?ml|ts|typoscript|tsconfig|dist|fla|in[ci]|log|sh|sql|sqlite)$".extraConfig = ''
deny all;
'';
# locations."~* /.*\.(?:bak|cfg|co?nf|ya?ml|ts)$".extraConfig = ''
# deny all;
# '';
# TYPO3 - Block access to recycler and temporary directories
locations."~ _(?:recycler|temp)_/".extraConfig = ''
deny all;
'';
# TYPO3 - Block access to configuration files stored in fileadmin
locations."~ fileadmin/(?:templates)/.*\\.(?:txt|ts|typoscript)$".extraConfig = ''
deny all;
'';
# TYPO3 - Block access to libraries, source and temporary compiled data
locations."~ ^(?:vendor|typo3_src|typo3temp/var)".extraConfig = ''
deny all;
'';
# TYPO3 - Block access to protected extension directories
locations."~ (?:typo3conf/ext|typo3/sysext|typo3/ext)/[^/]+/(?:Configuration|Resources/Private|Tests?|Documentation|docs?)/".extraConfig = ''
deny all;
'';
# Cache.appcache, your document html and data
locations."~* \\.(?:manifest|appcache|html?|xml|json)$".extraConfig = ''
expires -1;
# access_log logs/static.log; # I don't usually include a static log
'';
# Cache Media: images, icons, video, audio, HTC
locations."~* \\.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc|woff2)$".extraConfig = ''
expires 1y;
access_log off;
add_header Cache-Control "public";
'';
# Feed
locations."~* \\.(?:rss|atom)$".extraConfig = ''
expires 1h;
add_header Cache-Control "public";
'';
# Cache CSS, Javascript, Images, Icons, Video, Audio, HTC, Fonts
locations."~* \\.(?:css|js|jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc|woff2)$".extraConfig = ''
expires 1y;
access_log off;
add_header Cache-Control "public";
'';
locations."/".extraConfig = ''
index index.php index.html;
try_files $uri $uri/ /index.php$is_args$args;
'';
# TYPO3 Backend URLs
locations."/typo3$".extraConfig = ''
rewrite ^ /typo3/;
'';
locations."/typo3/".extraConfig = ''
try_files $uri /typo3/index.php$is_args$args;
'';
locations."~ [^/]\\.php(/|$)".extraConfig = ''
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
if (!-f $document_root$fastcgi_script_name) {
return 404;
}
include ${pkgs.nginx}/conf/fastcgi_params;
include ${pkgs.nginx}/conf/fastcgi.conf;
fastcgi_buffer_size 32k;
fastcgi_buffers 8 16k;
fastcgi_connect_timeout 240s;
fastcgi_read_timeout 240s;
fastcgi_send_timeout 240s;
fastcgi_pass unix:${config.services.phpfpm.pools."${domain}".socket};
fastcgi_index index.php;
'';
}
) cfg.instances;
config.users.users = mapAttrs' (instance: instanceOpts:
let
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
user = if instanceOpts.user != null
then instanceOps.user
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
in
nameValuePair user {
isNormalUser = true;
createHome = true;
home = "/var/www/" + domain;
homeMode= "770";
group = config.services.nginx.group;
openssh.authorizedKeys.keys = instanceOpts.authorizedKeys;
}
) cfg.instances;
config.users.groups = mapAttrs' (instance: instanceOpts:
let
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
user = if instanceOpts.user != null
then instanceOps.user
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
in nameValuePair user {}) cfg.instances;
config.services.mysql.ensureUsers = mapAttrsToList (instance: instanceOpts:
let
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
user = if instanceOpts.user != null
then instanceOps.user
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
in
{
name = user;
ensurePermissions = {
"${user}.*" = "ALL PRIVILEGES";
};
}) cfg.instances;
config.services.mysql.ensureDatabases = mapAttrsToList (instance: instanceOpts:
let
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
user = if instanceOpts.user != null
then instanceOps.user
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
in
user
) cfg.instances;
config.services.mysqlBackup.databases = mapAttrsToList (instance: instanceOpts:
let
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
user = if instanceOpts.user != null
then instanceOps.user
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
in
user
) cfg.instances;
}

View File

@@ -0,0 +1,38 @@
{ config, pkgs, ... }:
{
# security.rtkit.enable = true;
# services.pipewire = {
# enable = true;
# alsa.enable = true;
# alsa.support32Bit = true;
# pulse.enable = true;
#
# };
sound.enable = true;
hardware.pulseaudio.enable = true;
hardware.pulseaudio.support32Bit = true;
services.getty.autologinUser = "snapclient";
users.groups.snapclient = {};
users.users.snapclient = {
isNormalUser = true;
group = "snapclient";
extraGroups = [ "audio" "pipewire" ];
};
systemd.user.services.snapclient = {
wantedBy = [
"default.target"
];
after = [
"network.target"
];
serviceConfig = {
# User = "snapclient";
# Group = "snapclient";
ExecStart = "${pkgs.snapcast}/bin/snapclient -h mopidy.cloonar.com -p 1704 --player pulse";
Restart = "on-failure";
};
};
}

View File

@@ -0,0 +1,80 @@
{ pkgs, config, python3Packages, ... }:
let
shairport-sync = pkgs.shairport-sync.overrideAttrs (_: {
configureFlags = [
"--with-alsa" "--with-pipe" "--with-pa" "--with-stdout"
"--with-avahi" "--with-ssl=openssl" "--with-soxr"
"--without-configfiles"
"--sysconfdir=/etc"
"--with-metadata"
];
});
in
{
services.snapserver = {
enable = true;
codec = "flac";
http.docRoot = "${pkgs.snapcast}/share/snapserver/snapweb";
streams.mopidy = {
type = "pipe";
location = "/run/snapserver/mopidy";
};
streams.airplay = {
type = "airplay";
location = "${shairport-sync}/bin/shairport-sync";
query = {
devicename = "Multi Room";
port = "5000";
params = "--mdns=avahi";
};
};
streams.mixed = {
type = "meta";
location = "/airplay/mopidy";
};
};
services.avahi.enable = true;
services.avahi.publish.enable = true;
services.avahi.publish.userServices = true;
# services.shairport-sync = {
# enable = true;
# arguments = "-v -o=pipe -- pipe:name=/run/snapserver/airplay";
# };
services.nginx.virtualHosts."snapcast.cloonar.com" = {
forceSSL = true;
enableACME = true;
acmeRoot = null;
extraConfig = ''
proxy_buffering off;
'';
locations."/".extraConfig = ''
proxy_pass http://127.0.0.1:1780;
proxy_set_header Host $host;
proxy_redirect http:// https://;
proxy_http_version 1.1;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
'';
};
networking.firewall.allowedTCPPorts = [
80 # http
443 # https
1704 # snapcast
1705 # snapcast
5000 # airplay
5353 # airplay
];
networking.firewall.allowedUDPPorts = [
5000 # airplay
5353 # airplay
];
networking.firewall.allowedUDPPortRanges = [
{ from = 6001; to = 6011; } # airplay
];
}

6
utils/modules/sops.nix Normal file
View File

@@ -0,0 +1,6 @@
{
imports = [
"${builtins.fetchTarball "https://github.com/Mic92/sops-nix/archive/master.tar.gz"}/modules/sops"
];
}

View File

@@ -0,0 +1,7 @@
{ pkgs, ... }:
{
environment.systemPackages = [
pkgs.apache-directory-studio
];
}

View File

@@ -0,0 +1,17 @@
{ pkgs, ... }:
let
unstable = import (fetchTarball https://nixos.org/channels/nixos-unstable/nixexprs.tar.xz) {
config = { allowUnfree = true; };
};
parsecDesktopItem = pkgs.makeDesktopItem {
name = "parsec";
desktopName = "Parsec Gaming";
exec = "parsecd";
};
in {
environment.systemPackages = with pkgs; [
unstable.parsec-bin
parsecDesktopItem
];
}

View File

@@ -0,0 +1,17 @@
{ pkgs, ... }:
let
signalWorkDesktopItem = pkgs.makeDesktopItem {
name = "signal-work";
desktopName = "Signal with work profile";
icon = "signal-desktop";
exec = "signal-desktop --user-data-dir=/home/dominik/.config/Signal-work -- %u";
};
in {
environment.systemPackages = [
pkgs.signal-desktop
pkgs.signal-cli
signalWorkDesktopItem
];
}

View File

@@ -0,0 +1,12 @@
{ pkgs, ... }:
let
socialDesktopItem = pkgs.makeDesktopItem {
name = "social";
desktopName = "Firefox browser with social profile";
exec = "firefox -P social";
};
in {
environment.systemPackages = [ socialDesktopItem ];
}

View File

@@ -0,0 +1,350 @@
# Oxide theme
#
# Author: Diki Ananta <diki1aap@gmail.com> Repository: https://github.com/dikiaap/dotfiles
# License: MIT
# i3 config file (v4)
# font for window titles and bar
font pango:Source Sans Pro 10
# use win key
set $mod Mod4
# use these keys for focus, movement, and resize directions
set $left h
set $down j
set $up k
set $right l
# define names for workspaces
set $ws1 "1: "
set $ws2 "2: "
set $ws3 "3: "
set $ws4 "4: "
set $ws5 "5: "
set $ws6 "6: "
set $ws7 "7: "
set $ws8 "8: "
set $ws9 "9: "
set $ws10 "10: "
# assign workspaces to primary monitor
workspace $ws1 output primary
workspace $ws2 output primary
workspace $ws3 output primary
workspace $ws4 output primary
workspace $ws5 output primary
workspace $ws6 output primary
workspace $ws7 output primary
workspace $ws8 output primary
workspace $ws9 output primary
# use $mod+Mouse to drag floating windows to their wanted position
floating_modifier $mod
# control focused window when follows the mouse movements
focus_follows_mouse no
# window border settings
default_border none
default_floating_border none
# hiding borders adjacent to the screen edges
hide_edge_borders none
# set popups during fullscreen mode
popup_during_fullscreen smart
# start a terminal
#bindsym $mod+Return workspace $ws1; exec --no-startup-id alacritty
bindsym $mod+Return workspace $ws1; exec alacritty -t alacritty-sway
# start a program launcher
# bindsym $mod+d exec --no-startup-id wofi -s /etc/wofi/style.css --show drun --lines 5 --columns 1 -t alacritty
bindsym $mod+d exec alacritty --class=launcher -e env TERMINAL_COMMAND="alacritty -e" sway-launcher-desktop
#bindsym $mod+d exec --no-startup-id bemenu
#bindsym $mod+d exec --no-startup-id i3-dmenu-desktop --dmenu="dmenu -i -fn 'Source Sans Pro-10' -nb '#212121' -sb '#2b83a6' -sf '#ffffff'
#bindsym $mod+d exec --no-startup-id bemenu --dmenu="dmenu -i -fn 'Source Sans Pro-10' -nb '#212121' -sb '#2b83a6' -sf '#ffffff'
# start an explorer
bindsym $mod+e exec --no-startup-id pcmanfm
# switching window with win+tab
bindsym $mod+Tab exec --no-startup-id wofi -show window
# kill focused window
bindsym $mod+Shift+q kill
# change keyboard layout
bindsym $mod+i swaymsg input "*" us
bindsym $mod+Shift+i swaymsg input "*" de
# change focus
bindsym $mod+$left focus left
bindsym $mod+$down focus down
bindsym $mod+$up focus up
bindsym $mod+$right focus right
# alternatively, you can use the cursor keys:
bindsym $mod+Left focus left
bindsym $mod+Down focus down
bindsym $mod+Up focus up
bindsym $mod+Right focus right
# move focused window
bindsym $mod+Shift+$left move left
bindsym $mod+Shift+$down move down
bindsym $mod+Shift+$up move up
bindsym $mod+Shift+$right move right
# alternatively, you can use the cursor keys:
bindsym $mod+Shift+Left move left
bindsym $mod+Shift+Down move down
bindsym $mod+Shift+Up move up
bindsym $mod+Shift+Right move right
# split in horizontal orientation
bindsym $mod+c split h
# split in vertical orientation
bindsym $mod+v split v
# enter fullscreen mode for the focused container
bindsym $mod+f fullscreen toggle
# change container layout
bindsym $mod+s layout stacking
bindsym $mod+w layout tabbed
bindsym $mod+p layout toggle split
# toggle tiling / floating
# bindsym $mod+Shift+space floating toggle
# change focus between tiling / floating windows
# bindsym $mod+space focus mode_toggle
# focus the parent container
bindsym $mod+a focus parent
# focus the child container
bindsym $mod+Shift+a focus child
# move the currently focused window to the scratchpad
bindsym $mod+Shift+minus move scratchpad
# show the next scratchpad window or hide the focused scratchpad window
bindsym $mod+minus scratchpad show
# move focused floating window to the current position of the cursor
bindsym $mod+Shift+m move position mouse
# set a window to stick to the glass
bindsym $mod+Shift+s sticky toggle
# sticky preview for media
bindsym $mod+Shift+p fullscreen disable; floating enable; resize set 350 px 197 px; sticky enable; move window to position 1006 px 537 px
# shortcut to change window border
bindsym $mod+t border normal 0
bindsym $mod+y border pixel 1
bindsym $mod+u border none
# switch to workspace
bindcode $mod+10 workspace $ws1
bindcode $mod+11 workspace $ws2
bindcode $mod+12 workspace $ws3
bindcode $mod+13 workspace $ws4
bindcode $mod+14 workspace $ws5
bindcode $mod+15 workspace $ws6
bindcode $mod+16 workspace $ws7
bindcode $mod+17 workspace $ws8
bindcode $mod+18 workspace $ws9
bindcode $mod+19 workspace $ws10
# move focused container to workspace
bindcode $mod+Shift+10 move container to workspace $ws1
bindcode $mod+Shift+11 move container to workspace $ws2
bindcode $mod+Shift+12 move container to workspace $ws3
bindcode $mod+Shift+13 move container to workspace $ws4
bindcode $mod+Shift+14 move container to workspace $ws5
bindcode $mod+Shift+15 move container to workspace $ws6
bindcode $mod+Shift+16 move container to workspace $ws7
bindcode $mod+Shift+17 move container to workspace $ws8
bindcode $mod+Shift+18 move container to workspace $ws9
bindcode $mod+Shift+19 move container to workspace $ws10
# shortcut applications
bindsym $mod+F1 exec gkamus
bindsym $mod+F2 workspace $ws2; exec subl
bindsym $mod+F3 workspace $ws3; exec chromium-browser
bindsym $mod+F4 workspace $ws4; exec pcmanfm
bindsym $mod+F5 workspace $ws5; exec evince
bindsym $mod+F6 workspace $ws6; exec audacious
bindsym $mod+F7 workspace $ws7; exec gcolor2
bindsym $mod+F8 workspace $ws8; exec telegram
bindsym $mod+F9 workspace $ws9; exec go-for-it
bindsym Print exec --no-startup-id gnome-screenshot
bindcode $mod+9 exec swaylock --image ~/.wallpaper.jpg
bindsym Ctrl+Shift+Space exec 1password --quick-access
bindsym $mod+Space exec rofi-rbw --menu-keybindings ctrl+p:copy:password
# volume
bindsym XF86AudioLowerVolume exec amixer -q sset Master 5%- unmute
bindsym XF86AudioRaiseVolume exec amixer -q sset Master 5%+ unmute
bindsym XF86AudioMute exec amixer -q sset Master toggle
# touchpad
# bindsym XF86NotificationCenter exec swaymsg input type:touchpad events toggle enabled disabled
# set brightness logarithmically by factor 1.4
# .72 is just slightly bigger than 1 / 1.4
bindsym --locked XF86MonBrightnessUp exec light -S "$(light -G | awk '{ print int(($1 + .72) * 1.4) }')"
bindsym --locked XF86MonBrightnessDown exec light -S "$(light -G | awk '{ print int($1 / 1.4) }')"
# reload the configuration file
bindsym $mod+Shift+c reload
# restart i3 inplace
bindsym $mod+Shift+r restart
# manage i3 session
bindsym $mod+Shift+e exec swaynag --background f1fa8c --border ffb86c --border-bottom-size 0 --button-background ffb86c --button-text 282a36 -t warning -f "pango:Hack 9" -m "Do you really want to exit?" -B "  Exit " "swaymsg exit" -B "  Lock " "pkill swaynag && swaylock --image ~/.wallpaper.jpg" -B "  Reboot " "pkill swaynag && reboot" -B "  Shutdown " "pkill swaynag && shutdown -h now" -B " Suspend " "pkill swaynag && systemctl suspend"
# resize window
bindsym $mod+r mode "  "
mode "  " {
# pressing left and up will shrink the window's width and height
# pressing right and down will grow the window's width and height
bindsym $left resize shrink width 10 px or 10 ppt
bindsym $down resize grow height 10 px or 10 ppt
bindsym $up resize shrink height 10 px or 10 ppt
bindsym $right resize grow width 10 px or 10 ppt
# same bindings, but for the arrow keys
bindsym Left resize shrink width 10 px or 10 ppt
bindsym Down resize grow height 10 px or 10 ppt
bindsym Up resize shrink height 10 px or 10 ppt
bindsym Right resize grow width 10 px or 10 ppt
# back to normal: Enter or win+r
bindsym Return mode "default"
bindsym $mod+r mode "default"
}
# set specific windows to floating mode
for_window [window_role="app"] floating enable
#for_window [window_role="pop-up"] floating enable
for_window [window_role="task_dialog"] floating enable
for_window [title="Preferences$"] floating enable
for_window [class="Gkamus"] floating enable
for_window [class="Go-for-it"] floating enable
for_window [class="Lightdm-gtk-greeter-settings"] floating enable
for_window [class="Lxappearance"] floating enable
for_window [class="Menu"] floating enable
for_window [class="Nm-connection-editor"] floating enable
for_window [class="Software-properties-gtk"] floating enable
for_window [app_id="launcher"] floating enable
# set specific windows to tabbed mode
#for_window [class="Rambox"]tabbed
#for_window [class="Signal"]tabbed
# assign program to workspace
assign [title="alacritty-sway"] → $ws1
assign [app_id="Alacritty" title="^(?!alacritty-sway)$"] → $ws2
assign [app_id="chromium"] → $ws3
assign [app_id="firefox"] → $ws3
assign [app_id="pcmanfm"] → $ws4
assign [app_id="libreoffice-calc"] → $ws5
assign [app_id="libreoffice-writer"] → $ws5
assign [class="vlc"] → $ws6
assign [class="Gimp"] → $ws7
assign [class="Signal"] → $ws8
assign [app_id="social"] → $ws8
assign [app_id="thunderbird"] → $ws8
assign [class="Lightdm-gtk-greeter-settings"] → $ws10
assign [class="Software-properties-gtk"] → $ws10
# class border backgr. text indicator child_border
client.focused #2b83a6 #2b83a6 #ffffff #dddddd #2b83a6
client.focused_inactive #212121 #212121 #86888c #292d2e #5a5a5a
client.unfocused #212121 #212121 #86888c #292d2e #5a5a5a
client.urgent #d64e4e #d64e4e #ffffff #d64e4e #d64e4e
client.placeholder #212121 #0c0c0c #ffffff #212121 #262626
client.background #212121
bar {
swaybar_command waybar
}
# gaps
smart_gaps on
gaps inner 12
gaps outer 0
# startup applications
exec /run/wrappers/bin/gnome-keyring-daemon --start --daemonize
exec dbus-sway-environment
exec configure-gtk
exec nm-applet --indicator
exec alacritty -t alacritty-sway
exec signal-desktop
exec firefox --name=social -P social
exec thunderbird
exec firefox
exec nextcloud
exec owncloud
exec swayidle \
before-sleep 'loginctl lock-session $XDG_SESSION_ID' \
lock 'swaylock --image ~/.wallpaper.jpg' \
timeout 180 'swaylock --screenshots --effect-blur 7x5' \
timeout 1800 'systemctl suspend'
exec dunst
#exec --no-startup-id swaybg -c "#000000" -m fill -i ~/.config/wallpaper/wot.jpg
# exec --no-startup-id gnome-keyring-daemon --start --components=pkcs11,secrets,ssh
exec 'sleep 2; swaymsg workspace $ws8; swaymsg layout tabbed'
# wallpaper
output eDP-1 bg ~/.wallpaper.jpg fill
output DP-4 bg ~/.wallpaper.jpg fill
output DP-5 bg ~/.wallpaper.jpg fill
input * xkb_layout "de"
input * xkb_variant "colemak,,typewriter"
input * xkb_options "grp:win_space_toggle"
input "MANUFACTURER1 Keyboard" xkb_model "pc101"
# notebook
set $laptop eDP-1
# bindswitch --reload --locked lid:on output $laptop disable
# bindswitch --reload lid:off output $laptop enable
# A lock command used in several places
set $lock_script swaylock
# A sleep command used in several places.
# We leave a bit of time for locking to happen before putting the system to sleep
set $sleep $lock_script && sleep 3 && systemctl suspend
# Triggers a short notification
set $notify dunstify --timeout 1500
# Set your laptop screen name
set $laptop_screen 'eDP-1'
# Clamshell mode or lock & sleep
# This is a if/else statement: [ outputs_count == 1 ] && true || false
#bindswitch --reload --locked lid:on exec '[ $(swaymsg -t get_outputs | grep name | wc -l) == 1 ] && ($sleep) || ($notify "Clamshell mode" "Laptop screen off" && swaymsg output $laptop_screen disable)'
#bindswitch --reload --locked lid:off output $laptop_screen enable
# disable xwayland
#xwayland disable
# Touchpad
input type:touchpad {
tap enabled
natural_scroll enabled
}

137
utils/modules/sway/sway.nix Normal file
View File

@@ -0,0 +1,137 @@
{ config, pkgs, lib, ... }:
let
dbus-sway-environment = pkgs.writeTextFile {
name = "dbus-sway-environment";
destination = "/bin/dbus-sway-environment";
executable = true;
text = ''
dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP=sway
systemctl --user stop pipewire pipewire-media-session xdg-desktop-portal xdg-desktop-portal-wlr
systemctl --user start pipewire pipewire-media-session xdg-desktop-portal xdg-desktop-portal-wlr
'';
};
in {
imports = [
./social.nix
./signal-work.nix
./thunderbird.nix
./parsec.nix
];
hardware.pulseaudio.enable = false;
services.xserver = {
enable = true;
excludePackages = [ pkgs.xterm ];
displayManager.gdm.enable = true;
displayManager.gdm.wayland = true;
# displayManager.sddm.enable = true;
displayManager.sessionPackages = [ pkgs.sway ];
displayManager.defaultSession = "sway";
libinput.enable = true;
desktopManager.gnome = {
enable = true;
extraGSettingsOverrides = ''
[org.gnome.desktop.interface]
gtk-theme='Dracula'
'';
};
};
services.teamviewer.enable = true;
services.gnome.gnome-keyring.enable = true;
environment.systemPackages = with pkgs; [
alsaUtils
alacritty
bitwarden
chromium
cryptomator
dbeaver
dbus-sway-environment
dracula-theme
gcc
git
glib
gimp
gnome.seahorse
gnome3.adwaita-icon-theme
grim
jmeter
libreoffice
mako
networkmanagerapplet
nextcloud-client
obs-studio
pavucontrol
pcmanfm
pinentry
rbw
rofi-rbw
rustdesk
slurp
sway
sway-launcher-desktop
swayidle
swaylock
# thunderbird
tor-browser-bundle-bin
unzip
vlc
waybar
wayland
wl-clipboard
wofi
wtype
apache-directory-studio
firefox
onlyoffice-bin
# kexi
];
# nixpkgs.config.permittedInsecurePackages = [
# "qtwebkit-5.212.0-alpha4"
# "electron-13.6.9"
# ];
# nixpkgs.config.allowBroken = true;
programs._1password-gui = {
enable = true;
polkitPolicyOwners = [ "dominik" ];
};
programs.light.enable = true;
fonts.fonts = with pkgs; [
noto-fonts
noto-fonts-cjk
noto-fonts-emoji
nerdfonts
];
security.rtkit.enable = true;
services.pipewire = {
enable = true;
alsa.enable = true;
alsa.support32Bit = true;
pulse.enable = true;
jack.enable = true;
};
programs.sway = {
enable = true;
wrapperFeatures.gtk = true;
};
environment.etc = {
"sway/config".source = "/etc/nixos/modules/sway/sway.conf";
"wofi/style.css".source = "/etc/nixos/modules/sway/wofi.css";
"xdg/waybar/config".source = "/etc/nixos/modules/sway/waybar.conf";
"xdg/waybar/style.css".source = "/etc/nixos/modules/sway/waybar.css";
};
}

View File

@@ -0,0 +1,39 @@
{ pkgs, ... }:
let
thunderbirdWorkDesktopItem = pkgs.makeDesktopItem {
name = "thunderbird-work";
desktopName = "Thunderbird Work";
icon = "thunderbird";
exec = "thunderbird -P Work";
};
thunderbirdCloonarDesktopItem = pkgs.makeDesktopItem {
name = "thunderbird-cloonar";
desktopName = "Thunderbird Cloonar";
icon = "thunderbird";
exec = "thunderbird -P Cloonar";
};
in
{
nixpkgs.overlays = [
(self: super:
{
thunderbird-bin-unwrapped = super.thunderbird-bin-unwrapped.overrideAttrs ( old: rec {
version = "112.0b7";
name = "thunderbird-bin";
src = super.fetchurl {
url = "https://download-installer.cdn.mozilla.net/pub/thunderbird/releases/${version}/linux-x86_64/en-US/thunderbird-${version}.tar.bz2";
sha256 = "30d23df34834096a79261439d5ea6d78d44921f0218893f100596ee6296cd806";
};
});
}
)
];
environment.systemPackages = [
# (import ../../pkgs/thunderbird.nix)
pkgs.thunderbird-bin-unwrapped
thunderbirdWorkDesktopItem
thunderbirdCloonarDesktopItem
];
}

View File

@@ -0,0 +1,132 @@
// -*- mode: json -*-
{
"layer": "top",
"position": "top",
"modules-left": [
"sway/workspaces",
"custom/right-arrow-dark"
],
"modules-center": [
"custom/left-arrow-dark",
"clock#1",
"custom/left-arrow-light",
"custom/left-arrow-dark",
"clock#2",
"custom/right-arrow-dark",
"custom/right-arrow-light",
"clock#3",
"custom/right-arrow-dark"
],
"modules-right": [
"custom/left-arrow-dark",
"network",
"custom/left-arrow-light",
"custom/left-arrow-dark",
"pulseaudio",
"custom/left-arrow-light",
"custom/left-arrow-dark",
"memory",
"custom/left-arrow-light",
"custom/left-arrow-dark",
"cpu",
"custom/left-arrow-light",
"custom/left-arrow-dark",
"battery",
"custom/left-arrow-light",
"custom/left-arrow-dark",
"disk",
"custom/left-arrow-light",
"custom/left-arrow-dark",
"tray"
],
"custom/left-arrow-dark": {
"format": "",
"tooltip": false
},
"custom/left-arrow-light": {
"format": "",
"tooltip": false
},
"custom/right-arrow-dark": {
"format": "",
"tooltip": false
},
"custom/right-arrow-light": {
"format": "",
"tooltip": false
},
"sway/workspaces": {
"disable-scroll": true,
"format": "{name}"
},
"clock#1": {
"format": "{:%a}",
"tooltip": false
},
"clock#2": {
"format": "{:%H:%M}",
"tooltip": false
},
"clock#3": {
"format": "{:%d.%m}",
"tooltip": false
},
"pulseaudio": {
"format": "{icon}{volume}%",
"format-muted": "ﱝ",
"format-icons": {
"phone": ["奄", "奔", "墳"],
"default": ["奄", "奔", "墳"]
},
"scroll-step": 5,
"on-click": "pavucontrol",
"tooltip": false
},
"network": {
// "interface": "wlp2*", // (Optional) To force the use of this interface
"format-wifi": "{essid} ({signalStrength}%) ",
"format-ethernet": "{ipaddr}/{cidr} ",
"tooltip-format": "{ifname} via {gwaddr} ",
"format-linked": "{ifname} (No IP) ",
"format-disconnected": "Disconnected ",
"format-alt": "{ifname}: {ipaddr}/{cidr}"
},
"memory": {
"interval": 5,
"format": "{}%"
},
"cpu": {
"interval": 5,
"format": "﬙{usage:2}%"
},
"battery": {
"states": {
"good": 95,
"warning": 30,
"critical": 15
},
"format": "{icon} {capacity}%",
"format-icons": [
"",
"",
"",
"",
""
]
},
"disk": {
"interval": 5,
"format": "{percentage_used:2}%",
"path": "/home/"
},
"tray": {
"icon-size": 20
}
}

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