switched modules to flakes

This commit is contained in:
Dennis Wuitz 2023-12-24 18:48:52 +01:00
parent aca834a717
commit 469038e980
37 changed files with 150 additions and 2197 deletions

93
flake.lock generated Normal file
View File

@ -0,0 +1,93 @@
{
"nodes": {
"nix-index-database": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1703387252,
"narHash": "sha256-XKJqGj0BaEn/zyctEnkgVIh6Ba1rgTRc+UBi9EU8Y54=",
"owner": "Mic92",
"repo": "nix-index-database",
"rev": "f4340c1a42c38d79293ba69bfd839fbd6268a538",
"type": "github"
},
"original": {
"owner": "Mic92",
"repo": "nix-index-database",
"type": "github"
}
},
"nixos-modules": {
"inputs": {
"nixpkgs-lib": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1703426109,
"narHash": "sha256-/ujCgLJUq+iMgrvMVj58uvXan/nKvG0SeNVVrsvzJHk=",
"owner": "SuperSandro2000",
"repo": "nixos-modules",
"rev": "caa008d22e663c6190ffe12286566b2e87f357e4",
"type": "github"
},
"original": {
"owner": "SuperSandro2000",
"repo": "nixos-modules",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1703255338,
"narHash": "sha256-Z6wfYJQKmDN9xciTwU3cOiOk+NElxdZwy/FiHctCzjU=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "6df37dc6a77654682fe9f071c62b4242b5342e04",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nix-index-database": "nix-index-database",
"nixos-modules": "nixos-modules",
"nixpkgs": "nixpkgs",
"sops-nix": "sops-nix"
}
},
"sops-nix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"nixpkgs-stable": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1703387502,
"narHash": "sha256-JnWuQmyanPtF8c5yAEFXVWzaIlMxA3EAZCh8XNvnVqE=",
"owner": "Mic92",
"repo": "sops-nix",
"rev": "e523e89763ff45f0a6cf15bcb1092636b1da9ed3",
"type": "github"
},
"original": {
"owner": "Mic92",
"repo": "sops-nix",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

View File

@ -4,9 +4,17 @@
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
nixos-modules = {
url = "github:SuperSandro2000/nixos-modules";
inputs.nixpkgs-lib.follows = "nixpkgs";
};
sops-nix = {
url = "github:Mic92/sops-nix";
inputs.nixpkgs.follows = "nixpkgs";
inputs = {
nixpkgs.follows = "nixpkgs";
nixpkgs-stable.follows = "nixpkgs";
};
};
nix-index-database = {
@ -15,33 +23,36 @@
};
};
outputs = { nixpkgs, nix-index-database, sops-nix, ... }: {
src = builtins.filterSource (path: type: type == "directory" || lib.hasSuffix ".nix" (baseNameOf path)) ./.;
ls = dir: lib.attrNames (builtins.readDir (src + "/${dir}"));
fileList = dir: map (file: ./. + "/${dir}/${file}") (ls dir);
outputs = { nixpkgs, nixos-modules, nix-index-database, sops-nix, ... }:
let
inherit (nixpkgs) lib;
in {
nixosConfigurations = let
constructSystem = {
hostname,
system ? "x86_64-linux",
modules ? [],
users ? [],
}: nixpkgs.lib.nixosSystem {
inherit system hostname;
}: lib.nixosSystem {
inherit system;
modules = [
nixos-modules.nixosModule
sops-nix.nixosModules.sops
nix-index-database.nixosModules.nix-index
./system/programs.nix
./system/configuration.nix
./system/${hostname}/configuration.nix
] ++ fileList "modules" ++ modules ++ map (user: ./users/${user}/default.nix ) users;
./systems/programs.nix
./systems/configuration.nix
./systems/${hostname}/configuration.nix
] ++ modules ++ map(user: ./users/${user}) users;
};
in {
photon = constructSystem {
hostname = "photon"
hostname = "photon";
};
palatine-hill = constructSystem {
hostname = "palatine-hill"
hostname = "palatine-hill";
};
};
};

View File

@ -1,9 +0,0 @@
{ lib, ... }:
{
mkUserGroupOption = lib.mkOption {
type = with lib.types; nullOr str;
default = null;
description = lib.mdDoc "Restrict logins to users in this group";
};
}

View File

@ -1,11 +0,0 @@
{ config, lib, ... }:
{
mkOpinionatedOption = text: lib.mkOption {
type = lib.types.bool;
default = config.opinionatedDefaults;
description = lib.mdDoc "Whether to ${text}.";
};
mkRecursiveDefault = lib.mapAttrsRecursive (_: lib.mkDefault);
}

View File

@ -1,8 +0,0 @@
{ lib, ... }:
{
# taken from https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/services/misc/nix-daemon.nix#L828-L832
# a builder can run code for `gcc.arch` and inferior architectures
gcc-system-features = arch: [ "gccarch-${arch}" ]
++ map (x: "gccarch-${x}") lib.systems.architectures.inferiors.${arch};
}

View File

@ -1,10 +0,0 @@
_:
{
mkPubKey = name: type: publicKey: {
"${name}-${type}" = {
extraHostNames = [ name ];
publicKey = "${type} ${publicKey}";
};
};
}

View File

@ -1,21 +0,0 @@
{ config, lib, ... }:
let
cfg = config.security.acme;
in
{
options.security.acme.staging = lib.mkOption {
type = lib.types.bool;
default = false;
description = lib.mdDoc ''
If set to true, use Let's Encrypt's staging environment instead of the production one.
The staging environment has much higher rate limits but does not generate fully signed certificates.
This is great for testing when the normla rate limit is hit fast and impacts other people on the same IP.
See <literal>https://letsencrypt.org/docs/staging-environment</literal> for more detail.
'';
};
config = lib.mkIf cfg.staging {
security.acme.server = "https://acme-staging-v02.api.letsencrypt.org/directory";
};
}

View File

@ -1,41 +0,0 @@
{ config, lib, ... }:
let
cfg = config.boot;
in
{
options = {
boot = {
default = lib.mkOpinionatedOption "enable the boot builder";
};
};
cfg = lib.mkIf cfg.default {
supportedFilesystems = [ "zfs" ];
tmp.useTmpfs = true;
kernelPackages = config.boot.zfs.package.latestCompatibleLinuxPackages;
kernelParams = [ "kvm-amd" "nordrand" ];
zfs = {
enableUnstable = true;
devNodes = "/dev/disk/by-id/";
forceImportRoot = true;
};
loader = {
efi = {
canTouchEfiVariables = false;
efiSysMountPoint = "/boot/efis/nvme-Samsung_SSD_980_PRO_1TB_S5GXNF0W178262L-part1";
};
generationsDir.copyKernels = true;
grub = {
enable = true;
copyKernels = true;
zfsSupport = true;
efiSupport = true;
efiInstallAsRemovable = true;
fsIdentifier = "uuid";
device = "nodev";
extraInstallCommands = "[ ! -e /boot/efis/nvme-Samsung_SSD_980_PRO_1TB_S5GXNF0W178262L-part1/EFI ] || cp -r /boot/efis/nvme-Samsung_SSD_980_PRO_1TB_S5GXNF0W178262L-part1/EFI/* /boot/efis/nvme-Samsung_SSD_980_PRO_1TB_S5GXNF0W178262L-part1";
};
};
};
}

View File

@ -1,56 +0,0 @@
{ config, lib, libS, ... }:
let
cfg = config.virtualisation;
cfgd = cfg.docker;
cfgp = cfg.podman;
in
{
options.virtualisation = {
docker = {
aggresiveAutoPrune = libS.mkOpinionatedOption "configure aggresive auto prune which removes everything unreferenced by running containers. This includes named volumes and mounts should be used instead";
recommendedDefaults = libS.mkOpinionatedOption "set recommended and maintenance reducing default settings";
};
podman.recommendedDefaults = libS.mkOpinionatedOption "set recommended and maintenance reducing default settings";
};
config = {
virtualisation = {
containers.registries.search = lib.mkIf cfgp.recommendedDefaults [
"docker.io"
"quay.io"
"ghcr.io"
"gcr.io"
];
docker = {
daemon.settings = let
useIPTables = !config.networking.nftables.enable;
in lib.mkIf cfgd.recommendedDefaults {
fixed-cidr-v6 = "fd00::/80"; # TODO: is this a good idea for all networks?
iptables = useIPTables;
ip6tables = useIPTables;
ipv6 = true;
# userland proxy is slow, does not give back ports and if iptables/nftables is avaible just worsefgd.aggresiveAutoPrune
userland-proxy = false;
};
autoPrune = lib.mkIf cfgd.aggresiveAutoPrune {
enable = true;
flags = [
"--all"
"--external"
"--force"
"--volumes"
];
};
};
podman = {
autoPrune.enable = lib.mkIf cfgp.recommendedDefaults true;
defaultNetwork.settings.dns_enabled = lib.mkIf cfgp.recommendedDefaults true;
};
};
};
}

View File

@ -1,147 +0,0 @@
{ config, lib, libS, ... }:
let
cfg = config.services.gitea;
cfgl = cfg.ldap;
inherit (config.security) ldap;
in
{
options = {
services.gitea = {
# based on https://github.com/majewsky/nixos-modules/blob/master/gitea.nix
ldap = {
enable = lib.mkEnableOption (lib.mdDoc "login via ldap");
adminGroup = lib.mkOption {
type = with lib.types; nullOr str;
default = null;
example = "gitea-admins";
description = lib.mdDoc "Name of the ldap group that grants admin access in gitea.";
};
bindPasswordFile = lib.mkOption {
type = with lib.types; nullOr str;
default = null;
example = "/var/lib/secrets/bind-password";
description = lib.mdDoc "Path to a file containing the bind password.";
};
userGroup = libS.ldap.mkUserGroupOption;
options =
let
mkOptStr = lib.mkOption {
type = with lib.types; nullOr str;
default = null;
};
in
{
id = lib.mkOption {
type = lib.types.ints.unsigned;
default = 1;
};
name = mkOptStr;
security-protocol = mkOptStr;
host = mkOptStr;
port = lib.mkOption {
type = with lib.types; nullOr port;
default = null;
};
bind-dn = mkOptStr;
bind-password = mkOptStr;
user-search-base = mkOptStr;
user-filter = mkOptStr;
admin-filter = mkOptStr;
username-attribute = mkOptStr;
firstname-attribute = mkOptStr;
surname-attribute = mkOptStr;
email-attribute = mkOptStr;
public-ssh-key-attribute = mkOptStr;
};
};
recommendedDefaults = libS.mkOpinionatedOption "set recommended, secure default settings";
};
};
config.services.gitea = lib.mkIf (cfg.enable && cfgl.enable) {
ldap.options = {
name = "ldap";
security-protocol = "LDAPS";
host = ldap.domainName;
inherit (ldap) port;
bind-dn = ldap.bindDN;
bind-password = "$(cat ${cfgl.bindPasswordFile})";
user-search-base = ldap.userBaseDN;
user-filter = ldap.searchFilterWithGroupFilter cfgl.userGroup (ldap.userFilter "%[1]s");
admin-filter = ldap.groupFilter cfgl.adminGroup;
username-attribute = ldap.userField;
firstname-attribute = ldap.givenNameField;
surname-attribute = ldap.surnameField;
email-attribute = ldap.mailField;
public-ssh-key-attribute = ldap.sshPublicKeyField;
};
settings = lib.mkIf cfg.recommendedDefaults (libS.modules.mkRecursiveDefault {
cors = {
ALLOW_DOMAIN = cfg.settings.server.DOMAIN;
ENABLED = true;
SCHEME = "https";
};
cron.ENABLED = true;
"cron.resync_all_sshkeys".ENABLED = true;
"cron.resync_all_hooks".ENABLED = true;
other.SHOW_FOOTER_VERSION = false;
repository.ACCESS_CONTROL_ALLOW_ORIGIN = cfg.settings.server.DOMAIN;
"repository.signing".DEFAULT_TRUST_MODEL = "committer";
security.DISABLE_GIT_HOOKS = true;
server = {
ENABLE_GZIP = true;
ROOT_URL = "https://${cfg.settings.server.DOMAIN}/";
SSH_SERVER_CIPHERS = "chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com";
SSH_SERVER_KEY_EXCHANGES = "curve25519-sha256@libssh.org, ecdh-sha2-nistp521, ecdh-sha2-nistp384, ecdh-sha2-nistp256, diffie-hellman-group14-sha1";
SSH_SERVER_MACS = "hmac-sha2-256-etm@openssh.com, hmac-sha2-256, hmac-sha1";
};
session = {
COOKIE_SECURE = true;
PROVIDER = "db";
SAME_SITE = "strict";
SESSION_LIFE_TIME = 28 * 86400; # 28 days
};
"ssh.minimum_key_sizes" = {
ECDSA = -1;
RSA = 4095;
};
time.DEFAULT_UI_LOCATION = config.time.timeZone;
});
};
config.systemd.services = lib.mkIf (cfg.enable && cfgl.enable) {
gitea.preStart =
let
exe = lib.getExe cfg.package;
# allow executing shell after the --bind-password argument to e.g. cat a password file
formatOption = key: value: "--${key} ${if key == "bind-password" then value else lib.escapeShellArg value}";
ldapOptionsStr = opt: lib.concatStringsSep " " (lib.mapAttrsToList formatOption opt);
commonArgs = "--attributes-in-bind --synchronize-users";
in
lib.mkAfter ''
if ${exe} admin auth list | grep -q ${cfgl.options.name}; then
${exe} admin auth update-ldap ${commonArgs} ${ldapOptionsStr cfgl.options}
else
${exe} admin auth add-ldap ${commonArgs} ${ldapOptionsStr (lib.filterAttrs (name: _: name != "id") cfgl.options)}
fi
'';
};
config.services.portunus.seedSettings.groups = [
(lib.mkIf (cfgl.adminGroup != null) {
long_name = "Gitea Administrators";
name = cfgl.adminGroup;
permissions = { };
})
(lib.mkIf (cfgl.userGroup != null) {
long_name = "Gitea Users";
name = cfgl.userGroup;
permissions = { };
})
];
}

View File

@ -1,130 +0,0 @@
{ config, lib, libS, ... }:
let
cfg = config.services.grafana;
in
{
options = {
services.grafana = {
configureNginx = lib.mkOption {
type = lib.types.bool;
default = false;
description = lib.mdDoc "Wether to configure Nginx.";
};
oauth = {
enable = lib.mkEnableOption (lib.mdDoc ''login only via OAuth2'');
enableViewerRole = lib.mkOption {
type = lib.types.bool;
default = false;
description = lib.mdDoc "Wether to enable the fallback Viewer role when users do not have the user- or adminGroup.";
};
adminGroup = libS.ldap.mkUserGroupOption;
userGroup = libS.ldap.mkUserGroupOption;
};
recommendedDefaults = libS.mkOpinionatedOption "set recommended and secure default settings";
};
};
config = {
# the default values are hardcoded instead of using options. because I couldn't figure out how to extract them from the freeform type
assertions = lib.mkIf cfg.enable [
{
assertion = cfg.oauth.enable -> cfg.settings."auth.generic_oauth".client_secret != null;
message = ''
Setting services.grafana.oauth.enable to true requires to set services.grafana.settings."auth.generic_oauth".client_secret.
Use this `$__file{/path/to/some/secret}` syntax to reference secrets securely.
'';
}
{
assertion = cfg.settings.security.secret_key != "SW2YcwTIb9zpOOhoPsMm";
message = "services.grafana.settings.security.secret_key must be changed from it's insecure, default value!";
}
{
assertion = cfg.settings.security.admin_password != "admin";
message = "services.grafana.settings.security.admin_password must be changed from it's insecure, default value!";
}
];
services.grafana.settings = lib.mkMerge [
(lib.mkIf (cfg.enable && cfg.recommendedDefaults) (libS.modules.mkRecursiveDefault {
# no analytics, sorry, not sorry
analytics = {
# TODO: drop after https://github.com/NixOS/nixpkgs/pull/240323 is merged
check_for_updates = false;
feedback_links_enabled = false;
reporting_enabled = false;
};
log.level = "warn";
security = {
cookie_secure = true;
content_security_policy = true;
strict_transport_security = true;
};
server = {
enable_gzip = true;
root_url = "https://${cfg.settings.server.domain}";
};
}))
(lib.mkIf (cfg.enable && cfg.oauth.enable) {
"auth.generic_oauth" = let
inherit (config.services.dex.settings) issuer;
in {
enabled = true;
allow_assign_grafana_admin = true; # required for grafana-admins
allow_sign_up = true; # otherwise no new users can be created
api_url = "${issuer}/userinfo";
auth_url = "${issuer}/auth";
client_id = "grafana";
disable_login_form = true; # only allow OAuth
icon = "signin";
name = config.services.portunus.domain;
oauth_allow_insecure_email_lookup = true; # otherwise updating the mail in ldap will break login
oauth_auto_login = true; # redirect automatically to the only oauth provider
use_refresh_token = true;
role_attribute_path = "contains(groups[*], 'grafana-admins') && 'Admin' || contains(info.roles[*], 'grafana-user') && 'Editor'"
+ lib.optionalString cfg.oauth.enableViewerRole "|| 'Viewer'";
role_attribute_strict = true;
# https://dexidp.io/docs/custom-scopes-claims-clients/
scopes = "openid email groups profile offline_access";
token_url = "${issuer}/token";
};
server.protocol = "socket";
})
];
};
config.services.nginx = lib.mkIf (cfg.enable && cfg.configureNginx) {
upstreams.grafana.servers."unix:${cfg.settings.server.socket}" = {};
virtualHosts = {
"${cfg.settings.server.domain}".locations = {
"/".proxyPass = "http://grafana";
"/api/live/ws" = {
proxyPass = "http://grafana";
proxyWebsockets = true;
};
};
};
};
config.services.portunus = {
dex = lib.mkIf cfg.oauth.enable {
enable = true;
oidcClients = [{
callbackURL = "https://${cfg.settings.server.domain}/login/generic_oauth";
id = "grafana";
}];
};
seedSettings.groups = lib.optional (cfg.oauth.adminGroup != null) {
long_name = "Grafana Administrators";
name = cfg.oauth.adminGroup;
permissions = { };
} ++ lib.optional (cfg.oauth.userGroup != null) {
long_name = "Grafana Users";
name = cfg.oauth.userGroup;
permissions = { };
};
};
}

View File

@ -1,34 +0,0 @@
{ config, lib, libS, ... }:
let
cfg = config.services.hedgedoc.ldap;
inherit (config.security) ldap;
in
{
options = {
services.hedgedoc.ldap = {
enable = lib.mkEnableOption (lib.mdDoc ''
login only via LDAP.
Use `service.hedgedoc.environmentFile` in format `bindCredentials=password` to set the credentials used by the search user
'');
userGroup = libS.ldap.mkUserGroupOption;
};
};
config.services.hedgedoc.settings.ldap = lib.mkIf cfg.enable {
url = "ldaps://${ldap.domainName}:${toString ldap.port}";
bindDn = ldap.bindDN;
bindCredentials = "$bindCredentials";
searchBase = ldap.userBaseDN;
searchFilter = ldap.searchFilterWithGroupFilter cfg.userGroup (ldap.userFilter "{{username}}");
tlsca = "/etc/ssl/certs/ca-certificates.crt";
useridField = ldap.userField;
};
config.services.portunus.seedSettings.groups = lib.optional (cfg.userGroup != null) {
long_name = "Hedgedoc Users";
name = cfg.userGroup;
permissions = {};
};
}

View File

@ -1,103 +0,0 @@
{ config, lib, libS, pkgs, ... }:
let
cfg = config.services.home-assistant;
inherit (config.security) ldap;
in
{
options = {
services.home-assistant = {
ldap = {
enable = lib.mkEnableOption (lib.mdDoc ''login only via LDAP
::: {.note}
Only enable this after completing the onboarding!
:::
'');
userGroup = libS.ldap.mkUserGroupOption;
};
recommendedDefaults = libS.mkOpinionatedOption "set recommended default settings";
};
};
config.services.home-assistant = lib.mkMerge [
(lib.mkIf (cfg.enable && cfg.recommendedDefaults) {
config = {
automation = "!include automations.yaml";
default_config = { }; # yes, this is required...
homeassistant = {
auth_providers = lib.mkIf (!cfg.ldap.enable) [
{ type = "homeassistant"; }
];
temperature_unit = "C";
time_zone = config.time.timeZone;
unit_system = "metric";
};
};
})
(lib.mkIf (cfg.enable && cfg.ldap.enable) {
config.homeassistant.auth_providers = [{
type = "command_line";
# the script is not inheriting PATH from home-assistant
command = pkgs.resholve.mkDerivation {
pname = "ldap-auth-sh";
version = "unstable-2019-02-23";
src = pkgs.fetchFromGitHub {
owner = "bob1de";
repo = "ldap-auth-sh";
rev = "819f9233116e68b5af5a5f45167bcbb4ed412ed4";
hash = "sha256-+QjRP5SKUojaCv3lZX2Kv3wkaNvpWFd97phwsRlhroY=";
};
installPhase = ''
install -Dm755 ldap-auth.sh -t $out/bin
'';
solutions.default = {
fake.external = [ "on_auth_failure" "on_auth_success" ];
inputs = with pkgs; [ coreutils curl gnugrep gnused openldap ];
interpreter = "${pkgs.bash}/bin/bash";
keep."source:$CONFIG_FILE" = true;
scripts = [ "bin/ldap-auth.sh" ];
};
}+ "/bin/ldap-auth.sh";
args = [
# https://github.com/bob1de/ldap-auth-sh/blob/master/examples/home-assistant.cfg
(pkgs.writeText "config.cfg" /* shell */ ''
ATTRS="${ldap.userField}"
CLIENT="ldapsearch"
DEBUG=0
FILTER="${ldap.groupFilter "home-assistant-users"}"
NAME_ATTR="${ldap.userField}"
SCOPE="base"
SERVER="ldaps://${ldap.domainName}"
USERDN="uid=$(ldap_dn_escape "$username"),${ldap.userBaseDN}"
BASEDN="$USERDN"
on_auth_success() {
# print the meta entries for use in HA
if [ ! -z "$NAME_ATTR" ]; then
name=$(echo "$output" | ${lib.getExe pkgs.gnused} -nr "s/^\s*$NAME_ATTR:\s*(.+)\s*\$/\1/Ip")
[ -z "$name" ] || echo "name=$name"
fi
}
'')
];
meta = true;
}];
})
];
config.services.portunus.seedSettings.groups = lib.optional (cfg.ldap.userGroup != null) {
long_name = "Home-Assistant Users";
name = cfg.ldap.userGroup;
permissions = { };
};
config.systemd.tmpfiles.rules = lib.mkIf (cfg.enable && cfg.recommendedDefaults) [
"f ${cfg.configDir}/automations.yaml 0444 hass hass"
];
}

View File

@ -1,91 +0,0 @@
{ config, lib, libS, ... }:
let
cfg = config.services.hydra.ldap;
inherit (config.security) ldap;
in
{
options = {
services.hydra.ldap = {
enable = lib.mkEnableOption (lib.mdDoc ''
login only via LDAP.
The bind user password must be placed at `/var/lib/hydra/ldap-password.conf` in the format `bindpw = "PASSWORD"
It is recommended to use a password without special characters because the perl config parser has weird escaping rule like that comment characters `#` must be escape with backslash
'');
roleMappings = lib.mkOption {
type = with lib.types; listOf (attrsOf str);
example = [{ hydra-admins = "admins"; }];
default = [ ];
description = lib.mdDoc "Map LDAP groups to hydra permissions. See upstream doc, especially role_mapping.";
};
userGroup = libS.ldap.mkUserGroupOption;
};
};
config.services.hydra.extraConfig = lib.mkIf cfg.enable /* xml */ ''
# https://hydra.nixos.org/build/196107287/download/1/hydra/configuration.html#using-ldap-as-authentication-backend-optional
<ldap>
<config>
<credential>
class = Password
password_field = password
password_type = self_check
</credential>
<store>
class = LDAP
ldap_server = "${ldap.domainName}"
<ldap_server_options>
scheme = ldaps
timeout = 10
</ldap_server_options>
binddn = "${ldap.bindDN}"
include ldap-password.conf
start_tls = 0
<start_tls_options>
ciphers = TLS_AES_256_GCM_SHA384
sslversion = tlsv1_3
</start_tls_options>
user_basedn = "${ldap.userBaseDN}"
user_filter = "${ldap.searchFilterWithGroupFilter cfg.userGroup (ldap.userFilter "%s")}"
user_scope = one
user_field = ${ldap.userField}
<user_search_options>
deref = always
</user_search_options>
# Important for role mappings to work:
use_roles = 1
role_basedn = "${ldap.roleBaseDN}"
role_filter = "${ldap.roleFilter}"
role_scope = one
role_field = ${ldap.roleField}
role_value = ${ldap.roleValue}
<role_search_options>
deref = always
</role_search_options>
</store>
</config>
<role_mapping>
# Make all users in the hydra-admin group Hydra admins
# hydra-admins = admin
# Allow all users in the dev group to restart jobs and cancel builds
# dev = restart-jobs
# dev = cancel-build
${lib.concatStringsSep "\n" (lib.concatMap (lib.mapAttrsToList (name: value: "${name} = ${value}")) cfg.roleMappings)}
</role_mapping>
</ldap>
'';
config.services.portunus.seedSettings.groups = [
(lib.mkIf (cfg.userGroup != null) {
long_name = "Hydra Users";
name = cfg.userGroup;
permissions = { };
})
] ++ lib.flatten (map lib.attrValues (map (lib.mapAttrs (ldapGroup: _: {
long_name = "Hydra Role ${ldapGroup}";
name = ldapGroup;
permissions = { };
})) cfg.roleMappings));
}

View File

@ -1,23 +0,0 @@
{ config, lib, pkgs, ... }:
{
options.hardware = {
intelGPU = lib.mkEnableOption "" // { description = "Whether to add drivers for intel hardware acceleration."; };
};
config = {
hardware.opengl = {
extraPackages = with pkgs; lib.mkIf config.hardware.intelGPU [
intel-compute-runtime # OpenCL library for iGPU
# video encoding/decoding hardware acceleration
intel-media-driver # broadwell or newer
intel-vaapi-driver # older hardware like haswell
];
extraPackages32 = with pkgs.pkgsi686Linux; lib.mkIf config.hardware.intelGPU [
# video encoding/decoding hardware acceleration
intel-media-driver # broadwell or newer
intel-vaapi-driver # older hardware like haswell
];
};
};
}

View File

@ -1,146 +0,0 @@
{ config, lib, ... }:
let
cfg = config.security.ldap;
in
{
options.security.ldap = lib.mkOption {
type = lib.types.submodule {
options = {
bindDN = lib.mkOption {
type = lib.types.str;
example = "uid=search";
default = "uid=${cfg.searchUID}";
apply = s: s + "," + cfg.userBaseDN;
description = lib.mdDoc ''
The DN of the service user used by services.
The user base dn will be automatically appended.
'';
};
domainComponent = lib.mkOption {
type = with lib.types; listOf str;
example = [ "example" "com" ];
apply = dc: lib.removeSuffix "," (lib.concatMapStrings (x: "dc=${x},") dc);
description = lib.mdDoc ''
Domain component(s) (dc) represented as a list of strings.
Each entry will be prefixed with `dc=` and all are concatinated with `,`, except the last one.
The example would be concatinated to `dc=example,dc=com`
'';
};
domainName = lib.mkOption {
type = lib.types.str;
example = "auth.example.com";
description = lib.mdDoc "The domain name to connect to the ldap server.";
};
givenNameField = lib.mkOption {
type = lib.types.str;
example = "givenName";
description = lib.mdDoc "The attribute of the user object where to find its given name.";
};
groupFilter = lib.mkOption {
type = with lib.types; functionTo str;
example = lib.literalExpression ''group: "(&(objectclass=person)(isMemberOf=cn=''${group},''${config.security.ldap.roleBaseDN}"'';
description = lib.mdDoc "A function that returns a group filter that matches the first argument against the names of the groups the user is part of.";
};
mailField = lib.mkOption {
type = lib.types.str;
example = "mail";
description = lib.mdDoc "The attribute of the user object where to find its email.";
};
port = lib.mkOption {
type = lib.types.port;
example = "636";
description = lib.mdDoc "The port the ldap server listens on. Usually this is 389 for ldap and 636 for ldaps.";
};
roleBaseDN = lib.mkOption {
type = lib.types.str;
example = "ou=groups";
apply = s: s + "," + cfg.domainComponent;
description = lib.mdDoc ''
The directory path where applications should search for users.
Domain component will be automatically appended.
'';
};
roleField = lib.mkOption {
type = lib.types.str;
example = "cn";
description = lib.mdDoc "The attribute where the user account is listed in a group.";
};
roleFilter = lib.mkOption {
type = lib.types.str;
example = "(&(objectclass=groupOfNames)(member=%s))";
description = lib.mdDoc "Filter to get the groups of an user object.";
};
roleValue = lib.mkOption {
type = lib.types.str;
example = "dn";
description = lib.mdDoc "The attribute of the user object where to find its distinguished name.";
};
searchUID = lib.mkOption {
type = lib.types.str;
example = "search";
description = lib.mdDoc "The uid of the service user used by services, often referred as search user.";
};
searchFilterWithGroupFilter = lib.mkOption {
type = with lib.types; functionTo (functionTo str);
example = lib.literalExpression ''userFilterGroup: userFilter: if (userFilterGroup != null) then "(&''${config.security.ldap.groupFilter userFilterGroup})" else userFilter'';
description = lib.mdDoc ''
A function that returns a search filter that may include a group filter.
The first argument may be the group that is filtered upon or null.
If set to null no additional filtering is done. If set the supplied filter is combined with the user filter.
The second argument must be the user filter including the applications placeholders or ideally the userFilter option.
'';
};
sshPublicKeyField = lib.mkOption {
type = lib.types.str;
example = "sshPublicKey";
description = lib.mdDoc "The attribute of the user object where to find its ssh public key.";
};
surnameField = lib.mkOption {
type = lib.types.str;
example = "sn";
description = lib.mdDoc "The attribute of the user object where to find its surname.";
};
userBaseDN = lib.mkOption {
type = lib.types.str;
example = "ou=users";
apply = s: s + "," + cfg.domainComponent;
description = lib.mdDoc ''
The directory path where applications should search for users.
Domain component will be automatically appended.
'';
};
userField = lib.mkOption {
type = lib.types.str;
example = "uid";
description = lib.mdDoc "The attribute of the user object where to find its username.";
};
userFilter = lib.mkOption {
type = with lib.types; functionTo str;
example = ''param: "(&(objectclass=person)(|(uid=''${param})(mail=''${param})))"'';
description = lib.mdDoc "A function that returns a user search filter that uses the first argument as the placeholder.";
};
};
};
default = { };
description = "LDAP options used in other services.";
};
}

View File

@ -1,75 +0,0 @@
{ config, lib, libS, pkgs, ... }:
let
cfg = config.services.mastodon;
cfgl = cfg.ldap;
inherit (config.security) ldap;
in
{
options.services.mastodon = {
ldap = {
enable = lib.mkEnableOption (lib.mdDoc "login only via LDAP");
userGroup = libS.ldap.mkUserGroupOption;
};
enableBirdUITheme = lib.mkEnableOption (lib.mdDoc "Bird UI Theme");
};
config.services.mastodon = {
package = lib.mkIf cfg.enableBirdUITheme (pkgs.mastodon.overrideAttrs (_: with pkgs; let
src = pkgs.applyPatches {
src = fetchFromGitHub {
owner = "mstdn";
repo = "Bird-UI-Theme-Admins";
rev = "2f9921db746593f393c13f9b79e5b4c2e19b03bd";
hash = "sha256-+7FUm5GNXRWyS9Oiow6kwX+pWh11wO3stm5iOTY3sYY=";
};
patches = [
# fix compose box background
(fetchpatch {
url = "https://github.com/mstdn/Bird-UI-Theme-Admins/commit/d5a07d653680fba0ad8dd941405e2d0272ff9cd1.patch";
hash = "sha256-1gnQNCSSuTE/pkPCf49lJQbmeLAbaiPD9u/q8KiFvlU=";
})
];
};
in {
mastodonModules = mastodon.mastodonModules.overrideAttrs (oldAttrs: {
pname = "mastodon-birdui-theme";
nativeBuildInputs = oldAttrs.nativeBuildInputs ++ [
rsync
xorg.lndir
];
postPatch = ''
rsync -r ${src}/mastodon/ .
'';
});
postBuild = ''
cp ${src}/mastodon/config/themes.yml config/themes.yml
'';
}));
extraConfig = lib.mkIf cfgl.enable {
LDAP_ENABLED = "true";
LDAP_BASE = ldap.userBaseDN;
LDAP_BIND_DN = ldap.bindDN;
LDAP_HOST = ldap.domainName;
LDAP_METHOD = "simple_tls";
LDAP_PORT = toString ldap.port;
LDAP_UID = ldap.userField;
# convert .,- (space) in LDAP usernames to underscore, otherwise those users cannot log in
LDAP_UID_CONVERSION_ENABLED = "true";
LDAP_SEARCH_FILTER = ldap.searchFilterWithGroupFilter cfgl.userGroup "(|(%{uid}=%{email})(%{mail}=%{email}))";
};
};
config.services.portunus.seedSettings.groups = lib.optional (cfgl.userGroup != null) {
long_name = "Mastodon Users";
name = cfgl.userGroup;
permissions = { };
};
}

View File

@ -1,158 +0,0 @@
{ config, lib, libS, pkgs, ... }:
let
cfg = config.services.matrix-synapse;
cfge = cfg.element-web;
inherit (config.security) ldap;
in
{
options = {
services.matrix-synapse = {
addAdditionalOembedProvider = libS.mkOpinionatedOption "add additional oembed providers from oembed.com";
element-web = {
enable = lib.mkEnableOption (lib.mdDoc "the element-web client");
domain = lib.mkOption {
type = lib.types.str;
example = "element.example.org";
description = lib.mdDoc "The domain that element-web will use.";
};
package = lib.mkPackageOptionMD pkgs "Element-Web" {
default = [ "element-web" ];
};
enableConfigFeatures = libS.mkOpinionatedOption "enable most features available via config.json";
};
ldap = {
enable = lib.mkEnableOption (lib.mdDoc "login via ldap");
userGroup = libS.ldap.mkUserGroupOption;
bindPasswordFile = lib.mkOption {
type = lib.types.str;
example = "/var/lib/secrets/bind-password";
description = lib.mdDoc "Path to a file containing the bind password.";
};
};
recommendedDefaults = libS.mkOpinionatedOption "set recommended and secure default settings";
};
};
config.environment.etc = lib.mkIf cfg.enable {
"matrix-synapse/config.yaml".source = cfg.configFile;
};
config.services.nginx = lib.mkIf cfge.enable {
enable = true;
virtualHosts."${cfge.domain}" = {
forceSSL = true;
enableACME = lib.mkDefault true;
root = (cfge.package.override {
conf = with config.services.matrix-synapse.settings; {
default_server_config."m.homeserver" = {
"base_url" = public_baseurl;
"server_name" = server_name;
};
default_theme = "dark";
room_directory.servers = [ server_name ];
} // lib.optionalAttrs cfge.enableConfigFeatures {
features = {
# https://github.com/matrix-org/matrix-react-sdk/blob/develop/src/settings/Settings.tsx
# https://github.com/vector-im/element-web/blob/develop/docs/labs.md
feature_ask_to_join = true;
feature_bridge_state = true;
feature_exploring_public_spaces = true;
feature_jump_to_date = true;
feature_mjolnir = true;
feature_pinning = true;
feature_presence_in_room_list = true;
feature_report_to_moderators = true;
feature_qr_signin_reciprocate_show = true;
};
show_labs_settings = true;
};
}).overrideAttrs ({ postInstall ? "", ... }: {
# prevent 404 spam in nginx log
postInstall = postInstall + ''
ln -rs $out/config.json $out/config.${cfge.domain}.json
'';
});
};
};
config.services.matrix-synapse = lib.mkMerge [
{
settings = lib.mkIf cfge.enable rec {
email.client_base_url = web_client_location;
web_client_location = "https://${cfge.domain}";
};
}
(lib.mkIf cfg.ldap.enable {
plugins = with config.services.matrix-synapse.package.plugins; [
matrix-synapse-ldap3
];
settings.modules = [{
module = "ldap_auth_provider.LdapAuthProviderModule";
config = {
enabled = true;
mode = "search";
uri = "ldaps://${ldap.domainName}:${toString ldap.port}";
base = ldap.userBaseDN;
attributes = {
uid = ldap.userField;
mail = ldap.mailField;
name = ldap.givenNameField;
};
bind_dn = ldap.bindDN;
bind_password_file = cfg.ldap.bindPasswordFile;
tls_options.validate = true;
} // lib.optionalAttrs (cfg.ldap.userGroup != null) {
filter = ldap.groupFilter cfg.ldap.userGroup;
};
}];
})
{
settings.oembed.additional_providers = lib.mkIf cfg.addAdditionalOembedProvider [
(
let
providers = pkgs.fetchurl {
url = "https://oembed.com/providers.json?2023-03-23";
sha256 = "sha256-OdgBgkLbtNMn84ixKuC1gGzpyr+X+ORiLl6TAK3lYuQ=";
};
in
pkgs.runCommand "providers.json"
{
nativeBuildInputs = with pkgs; [ jq ];
} ''
# filter out entries that do not contain a schemes entry
# Error in configuration at 'oembed.additional_providers.<item 0>.<item 22>.endpoints.<item 0>': 'schemes' is a required property
# and have none http protocols: Unsupported oEmbed scheme (spotify) for pattern: spotify:*
jq '[ ..|objects| select(.endpoints[0]|has("schemes")) | .endpoints[0].schemes=([ .endpoints[0].schemes[]|select(.|contains("http")) ]) ]' ${providers} > $out
''
)
];
}
(lib.mkIf cfg.recommendedDefaults {
settings = {
federation_client_minimum_tls_version = "1.2";
suppress_key_server_warning = true;
user_directory.prefer_local_users = true;
};
withJemalloc = true;
})
];
config.services.portunus.seedSettings.groups = lib.optional (cfg.ldap.userGroup != null) {
long_name = "Matrix Users";
name = cfg.ldap.userGroup;
permissions = { };
};
}

View File

@ -1,218 +0,0 @@
{ config, lib, libS, pkgs, ... }:
let
cfg = config.services.nextcloud;
in
{
options = {
services.nextcloud = {
recommendedDefaults = libS.mkOpinionatedOption "set recommended default settings";
configureImaginary = libS.mkOpinionatedOption "configure and use Imaginary for preview generation";
configureMemories = libS.mkOpinionatedOption "configure dependencies for Memories App";
configureMemoriesVaapi = lib.mkOption {
type = lib.types.bool;
default = config.hardware.intelGPU;
defaultText = "config.hardware.intelGPU";
description = lib.mdDoc ''
Wether to configure Memories App to use an Intel iGPU for hardware acceleration.
'';
};
configurePreviewSettings = lib.mkOption {
type = lib.types.bool;
default = cfg.configureImaginary;
defaultText = "config.services.nextcloud.configureImaginary";
description = lib.mdDoc ''
Wether to configure the preview settings to be more optimised for real world usage.
By default this is enabled, when Imaginary is configured.
'';
};
configureRecognize = libS.mkOpinionatedOption "configure dependencies for Recognize App";
};
};
config = lib.mkIf cfg.enable {
services = {
imaginary = lib.mkIf cfg.configureImaginary {
enable = true;
address = "127.0.0.1";
settings.return-size = true;
};
nextcloud = {
# otherwise the Logging App does not function
logType = lib.mkIf cfg.recommendedDefaults "file";
phpOptions = lib.mkIf cfg.recommendedDefaults {
# https://docs.nextcloud.com/server/latest/admin_manual/installation/server_tuning.html#:~:text=opcache.jit%20%3D%201255%20opcache.jit_buffer_size%20%3D%20128m
"opcache.jit" = 1255;
"opcache.jit_buffer_size" = "128M";
};
extraOptions = lib.mkMerge [
(lib.mkIf cfg.configureImaginary {
enabledPreviewProviders = [
# default from https://github.com/nextcloud/server/blob/master/config/config.sample.php#L1295-L1304
''OC\Preview\BMP''
''OC\Preview\GIF''
''OC\Preview\JPEG''
''OC\Preview\Krita''
''OC\Preview\MarkDown''
''OC\Preview\MP3''
''OC\Preview\OpenDocument''
''OC\Preview\PNG''
''OC\Preview\TXT''
''OC\Preview\XBitmap''
# https://docs.nextcloud.com/server/24/admin_manual/installation/server_tuning.html#previews
''OC\Preview\Imaginary''
];
})
(lib.mkIf cfg.configureMemories {
enabledPreviewProviders = [
# https://github.com/pulsejet/memories/wiki/File-Type-Support
# TODO: not sure if this should be under configurePreviewSettings instead or both
''OC\Preview\Image'' # alias for png,jpeg,gif,bmp
''OC\Preview\HEIC''
''OC\Preview\TIFF''
''OC\Preview\Movie''
];
"memories.exiftool" = "${pkgs.exiftool}/bin/exiftool";
"memories.vod.vaapi" = lib.mkIf cfg.configureMemoriesVaapi true;
"memories.vod.ffmpeg" = "${pkgs.ffmpeg-headless}/bin/ffmpeg";
"memories.vod.ffprobe" = "${pkgs.ffmpeg-headless}/bin/ffprobe";
})
(lib.mkIf cfg.configurePreviewSettings {
enabledPreviewProviders = [
# https://github.com/nextcloud/server/tree/master/lib/private/Preview
''OC\Preview\Font''
''OC\Preview\PDF''
''OC\Preview\SVG''
''OC\Preview\WebP''
];
jpeg_quality = 60;
preview_max_filesize_image = 128; # MB
preview_max_memory = 512; # MB
preview_max_x = 2048; # px
preview_max_y = 2048; # px
})
];
};
phpfpm.pools = lib.mkIf cfg.configurePreviewSettings {
# add user packages to phpfpm process PATHs, required to find ffmpeg for preview generator
# beginning taken from https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/services/web-apps/nextcloud.nix#L985
nextcloud.phpEnv.PATH = lib.mkForce "/run/wrappers/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin:/usr/bin:/bin:/etc/profiles/per-user/nextcloud/bin";
};
};
systemd = {
services = {
nextcloud-cron = lib.mkIf cfg.configureMemories {
# required for memories
# see https://github.com/pulsejet/memories/blob/master/docs/troubleshooting.md#issues-with-nixos
path = with pkgs; [ perl ];
# fix memories app being unpacked without the x-bit on binaries
# could be done in nextcloud-update-plugins but then manually updates would be broken until the next auto update
preStart = "${pkgs.coreutils}/bin/chmod +x /var/lib/nextcloud/store-apps/memories/bin-ext/*";
};
nextcloud-cron-preview-generator = lib.mkIf cfg.configurePreviewSettings {
environment.NEXTCLOUD_CONFIG_DIR = "${config.services.nextcloud.datadir}/config";
serviceConfig = {
ExecStart = "/run/current-system/sw/bin/nextcloud-occ preview:pre-generate";
Type = "oneshot";
User = "nextcloud";
};
};
nextcloud-preview-generator-setup = lib.mkIf cfg.configurePreviewSettings {
wantedBy = [ "multi-user.target" ];
requires = [ "phpfpm-nextcloud.service" ];
after = [ "phpfpm-nextcloud.service" ];
environment.NEXTCLOUD_CONFIG_DIR = "${config.services.nextcloud.datadir}/config";
script =
let
occ = "/run/current-system/sw/bin/nextcloud-occ";
in
/* bash */ ''
# check with:
# for size in squareSizes widthSizes heightSizes; do echo -n "$size: "; nextcloud-occ config:app:get previewgenerator $size; done
# extra commands run for preview generator:
# 32 icon file list
# 64 icon file list android app, photos app
# 96 nextcloud client VFS windows file preview
# 256 file app grid view, many requests
# 512 photos app tags
${occ} config:app:set --value="32 64 96 256 512" previewgenerator squareSizes
# 341 hover in maps app
# 1920 files/photos app when viewing picture
${occ} config:app:set --value="341 1920" previewgenerator widthSizes
# 256 hover in maps app
# 1080 files/photos app when viewing picture
${occ} config:app:set --value="256 1080" previewgenerator heightSizes
'';
serviceConfig = {
Type = "oneshot";
User = "nextcloud";
};
};
nextcloud-setup = lib.mkIf cfg.configureRecognize {
script = /* bash */ ''
export PATH=$PATH:/etc/profiles/per-user/nextcloud/bin:/run/current-system/sw/bin
if [[ ! -e /var/lib/nextcloud/store-apps/recognize/node_modules/@tensorflow/tfjs-node/lib/napi-v8/tfjs_binding.node ]]; then
if [[ -d /var/lib/nextcloud/store-apps/recognize/node_modules/ ]]; then
cd /var/lib/nextcloud/store-apps/recognize/node_modules/
npm rebuild @tensorflow/tfjs-node --build-addon-from-source
fi
fi
'';
};
phpfpm-nextcloud.serviceConfig = lib.mkIf cfg.configureMemoriesVaapi {
DeviceAllow = [ "/dev/dri/renderD128 rwm" ];
PrivateDevices = lib.mkForce false;
};
};
timers.nextcloud-cron-preview-generator = lib.mkIf cfg.configurePreviewSettings {
timerConfig = {
OnUnitActiveSec = "5m";
Unit = "nextcloud-cron-preview-generator.service";
};
wantedBy = [ "timers.target" ];
};
};
users.users.nextcloud = {
extraGroups = lib.mkIf cfg.configureMemoriesVaapi [
"render" # access /dev/dri/renderD128
];
packages = with pkgs;
# generate video thumbnails with preview generator
lib.optional cfg.configurePreviewSettings ffmpeg-headless
# required for memories, duplicated with nextcloud-cron to better debug
++ lib.optional cfg.configureMemories perl
# required for recognize app
++ lib.optionals cfg.configureRecognize [
gnumake # installation requirement
nodejs_16 # runtime and installation requirement
nodejs_16.pkgs.node-pre-gyp # installation requirement
python3 # requirement for node-pre-gyp otherwise fails with exit code 236
util-linux # runtime requirement for taskset
];
};
};
}

View File

@ -1,176 +0,0 @@
{ config, lib, libS, pkgs, ... }:
let
cfg = config.services.nginx;
in
{
options.services.nginx = {
allCompression = libS.mkOpinionatedOption "set all recommended compression options";
default404Server = {
enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = lib.mdDoc ''
Wether to add a default server which always responds with 404.
This is useful when using a wildcard cname with a wildcard certitificate to not return the first server entry in the config on unknown subdomains
or to do the same for an old and not fully removed domain.
'';
};
acmeHost = lib.mkOption {
type = lib.types.str;
description = lib.mdDoc ''
The acme host to use for the default 404 server.
'';
};
};
generateDhparams = libS.mkOpinionatedOption "generate more secure, 2048 bits dhparams replacing the default 1024 bits";
openFirewall = libS.mkOpinionatedOption "open the firewall port for the http (80) and https (443) default ports";
quic = {
enable = lib.mkEnableOption (lib.mdDoc "quic support in nginx");
bpf = libS.mkOpinionatedOption "configure nginx' bpf support which routes quic packets from the same source to the same worker";
};
recommendedDefaults = libS.mkOpinionatedOption "set recommended performance options not grouped into other settings";
resolverAddrFromNameserver = libS.mkOpinionatedOption "set resolver address to environment.nameservers";
rotateLogsFaster = libS.mkOpinionatedOption "keep logs only for 7 days and rotate them daily";
setHSTSHeader = libS.mkOpinionatedOption "add the HSTS header to all virtual hosts";
tcpFastOpen = libS.mkOpinionatedOption "enable tcp fast open";
};
config = lib.mkIf cfg.enable {
assertions = [
{
assertion = cfg.quic.enable && cfg.quic.bpf -> !lib.versionOlder cfg.package.version "1.25.0";
message = "Setting services.nginx.quic.bpf to true requires nginx version 1.25.0 or newer, but currently \"${cfg.package.version}\" is used!";
}
];
boot.kernel.sysctl = lib.mkIf cfg.tcpFastOpen {
# enable tcp fastopen for outgoing and incoming connections
"net.ipv4.tcp_fastopen" = 3;
};
networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ 80 443 ];
nixpkgs.overlays = lib.mkIf cfg.tcpFastOpen [
(final: prev:
let
configureFlags = [ "-DTCP_FASTOPEN=23" ];
in
{
nginx = prev.nginx.override { inherit configureFlags; };
nginxQuic = prev.nginxQuic.override { inherit configureFlags; };
nginxStable = prev.nginxStable.override { inherit configureFlags; };
nginxMainline = prev.nginxMainline.override { inherit configureFlags; };
})
];
services = {
logrotate.settings.nginx = lib.mkIf cfg.rotateLogsFaster {
frequency = "daily";
rotate = 7;
};
# NOTE: do not use mkMerge here to prevent infinite recursions
nginx = {
appendConfig = lib.optionalString (cfg.quic.enable && cfg.quic.bpf) /* nginx */ ''
quic_bpf on;
'' + lib.optionalString cfg.recommendedDefaults /* nginx */ ''
worker_processes auto;
worker_cpu_affinity auto;
'';
commonHttpConfig = lib.optionalString cfg.recommendedDefaults /* nginx */ ''
error_log syslog:server=unix:/dev/log;
'' + lib.optionalString cfg.quic.enable /* nginx */''
quic_retry on;
'' + lib.optionalString cfg.recommendedZstdSettings /* nginx */ ''
# TODO: upstream this?
zstd_types application/x-nix-archive;
'';
commonServerConfig = lib.mkIf cfg.setHSTSHeader /* nginx */ ''
more_set_headers "Strict-Transport-Security: max-age=63072000; includeSubDomains; preload";
'';
package = lib.mkIf cfg.quic.enable pkgs.nginxQuic; # based on pkgs.nginxMainline
recommendedBrotliSettings = lib.mkIf cfg.allCompression true;
recommendedGzipSettings = lib.mkIf cfg.allCompression true;
recommendedOptimisation = lib.mkIf cfg.allCompression true;
recommendedProxySettings = lib.mkIf cfg.allCompression true;
recommendedTlsSettings = lib.mkIf cfg.allCompression true;
recommendedZstdSettings = lib.mkIf cfg.allCompression true;
resolver.addresses =
let
isIPv6 = addr: builtins.match ".*:.*:.*" addr != null;
escapeIPv6 = addr:
if isIPv6 addr then
"[${addr}]"
else
addr;
in
lib.optionals (cfg.resolverAddrFromNameserver && config.networking.nameservers != [ ]) (map escapeIPv6 config.networking.nameservers);
sslDhparam = lib.mkIf cfg.generateDhparams config.security.dhparams.params.nginx.path;
# NOTE: do not use mkMerge here to prevent infinite recursions
virtualHosts =
let
extraParameters = [
# net.core.somaxconn is set to 4096
# see https://www.nginx.com/blog/tuning-nginx/#:~:text=to%20a%20value-,greater%20than%20512,-%2C%20change%20the%20backlog
"backlog=1024"
"deferred"
"fastopen=256" # requires nginx to be compiled with -DTCP_FASTOPEN=23
];
in
lib.mkIf (cfg.recommendedDefaults || cfg.default404Server.enable || cfg.quic.enable) {
"_" = {
kTLS = lib.mkIf cfg.recommendedDefaults true;
reuseport = lib.mkIf (cfg.recommendedDefaults || cfg.quic.enable) true;
default = lib.mkIf cfg.default404Server.enable true;
forceSSL = lib.mkIf cfg.default404Server.enable true;
useACMEHost = lib.mkIf cfg.default404Server.enable cfg.default404Server.acmeHost;
extraConfig = lib.mkIf cfg.default404Server.enable /* nginx */ ''
return 404;
'';
listen = lib.mkIf cfg.tcpFastOpen (lib.mkDefault [
{ addr = "0.0.0.0"; port = 80; inherit extraParameters; }
{ addr = "0.0.0.0"; port = 443; ssl = true; inherit extraParameters; }
{ addr = "[::]"; port = 80; inherit extraParameters; }
{ addr = "[::]"; port = 443; ssl = true; inherit extraParameters; }
]);
quic = lib.mkIf cfg.quic.enable true;
};
};
};
};
security.dhparams = lib.mkIf cfg.generateDhparams {
enable = cfg.generateDhparams;
params.nginx = { };
};
systemd.services.nginx.serviceConfig = lib.mkIf (cfg.quic.enable && cfg.quic.bpf) {
# NOTE: CAP_BPF is included in CAP_SYS_ADMIN but it is not enough alone
AmbientCapabilities = [ "CAP_BPF" "CAP_NET_ADMIN" "CAP_SYS_ADMIN" ];
CapabilityBoundingSet = [ "CAP_BPF" "CAP_NET_ADMIN" "CAP_SYS_ADMIN" ];
SystemCallFilter = [ "bpf" ];
};
};
}

View File

@ -1,91 +0,0 @@
{ config, lib, libS, pkgs, ... }:
let
cfg = config.nix;
in
{
options.nix = {
deleteChannels = lib.mkEnableOption "" // { description = "Whether to delete all channels on a system switch."; };
deleteUserProfiles = lib.mkEnableOption "" // { description = "Whether to delete all user profiles on a system switch."; };
diffSystem = libS.mkOpinionatedOption "system closure diffing on updates";
recommendedDefaults = libS.mkOpinionatedOption "set recommended default settings";
remoteBuilder = {
enable = lib.mkEnableOption "restricted nix remote builder";
sshPublicKeys = lib.mkOption {
description = "SSH public keys accepted by the remote build user.";
type = lib.types.listOf lib.types.str;
};
name = lib.mkOption {
description = "Name of the user used for remote building.";
type = lib.types.str;
readOnly = true;
default = "nix-remote-builder";
};
};
};
config = {
# based on https://github.com/numtide/srvos/blob/main/nixos/roles/nix-remote-builder.nix
# and https://discourse.nixos.org/t/wrapper-to-restrict-builder-access-through-ssh-worth-upstreaming/25834
nix.settings = {
builders-use-substitutes = lib.mkIf cfg.recommendedDefaults true;
connect-timeout = lib.mkIf cfg.recommendedDefaults 20;
experimental-features = lib.mkIf cfg.recommendedDefaults [ "nix-command" "flakes" ];
trusted-users = lib.mkIf cfg.remoteBuilder.enable [ cfg.remoteBuilder.name ];
};
users.users.${cfg.remoteBuilder.name} = lib.mkIf cfg.remoteBuilder.enable {
group = "nogroup";
isNormalUser = true;
openssh.authorizedKeys.keys = map
(key:
let
wrapper-dispatch-ssh-nix = pkgs.writeShellScriptBin "wrapper-dispatch-ssh-nix" /* bash */ ''
case $SSH_ORIGINAL_COMMAND in
"nix-daemon --stdio")
exec ${config.nix.package}/bin/nix-daemon --stdio
;;
"nix-store --serve --write")
exec ${config.nix.package}/bin/nix-store --serve --write
;;
*)
echo "Access is only allowed for the nix remote builder" 1>&2
exit 1
esac
'';
in
"restrict,pty,command=\"${wrapper-dispatch-ssh-nix}/bin/wrapper-dispatch-ssh-nix\" ${key}"
)
config.nix.remoteBuilder.sshPublicKeys;
};
system.activationScripts = {
deleteChannels = lib.mkIf cfg.deleteChannels ''
echo "Deleting all channels..."
rm -rf /root/.nix-channels /home/*/.nix-channels /nix/var/nix/profiles/per-user/*/channels* || true
'';
deleteUserProfiles = lib.mkIf cfg.deleteUserProfiles ''
echo "Deleting all user profiles..."
rm -rf /root/.nix-profile /home/*/.nix-profile /nix/var/nix/profiles/per-user/*/profile* || true
'';
diff-system = lib.mkIf cfg.diffSystem {
supportsDryActivation = true;
text = ''
if [[ -e /run/current-system && -e $systemConfig ]]; then
echo System package diff:
${lib.getExe config.nix.package} --extra-experimental-features nix-command store diff-closures /run/current-system $systemConfig || true
fi
'';
};
};
};
}

View File

@ -1,9 +0,0 @@
{ config, lib, ... }:
let
cfg = config.services.openvpn;
in
{
# TODO: OpenVPN
}

View File

@ -1,25 +0,0 @@
diff --git a/internal/frontend/core.go b/internal/frontend/core.go
index 5976377..7c67991 100644
--- a/internal/frontend/core.go
+++ b/internal/frontend/core.go
@@ -43,8 +43,6 @@ func HTTPHandler(nexus core.Nexus, isBehindTLSProxy bool) http.Handler {
r.Methods("POST").Path(`/users/{uid}/delete`).Handler(postUserDeleteHandler(nexus))
r.Methods("GET").Path(`/groups`).Handler(getGroupsHandler(nexus))
- r.Methods("GET").Path(`/groups/new`).Handler(getGroupsNewHandler(nexus))
- r.Methods("POST").Path(`/groups/new`).Handler(postGroupsNewHandler(nexus))
r.Methods("GET").Path(`/groups/{name}/edit`).Handler(getGroupEditHandler(nexus))
r.Methods("POST").Path(`/groups/{name}/edit`).Handler(postGroupEditHandler(nexus))
r.Methods("GET").Path(`/groups/{name}/delete`).Handler(getGroupDeleteHandler(nexus))
diff --git a/internal/frontend/groups.go b/internal/frontend/groups.go
index 5ac6a75..ac59f4f 100644
--- a/internal/frontend/groups.go
+++ b/internal/frontend/groups.go
@@ -38,7 +38,6 @@ func getGroupsHandler(n core.Nexus) http.Handler {
<th>Members</th>
<th>Permissions granted</th>
<th class="actions">
- <a href="/groups/new" class="button button-primary">New group</a>
</th>
</tr>
</thead>

View File

@ -1,183 +0,0 @@
{ config, lib, pkgs, ... }:
let
cfg = config.services.portunus;
inherit (config.security) ldap;
in
{
options.services.portunus = {
# TODO: how to automatically set this?
# maybe based on $service.ldap.enable && services.portunus.enable?
addToHosts = lib.mkOption {
type = lib.types.bool;
default = false;
description = lib.mdDoc "Whether to add a hosts entry for the portunus domain pointing to externalIp";
};
configureOAuth2Proxy = lib.mkOption {
type = lib.types.bool;
default = false;
description = lib.mdDoc ''
Wether to configure OAuth2 Proxy with Portunus' Dex.
Use `services.oauth2_proxy.nginx.virtualHosts` to configure the nginx virtual hosts that should require authentication.
'';
};
internalIp4 = lib.mkOption {
type = with lib.types; nullOr str;
default = null;
description = lib.mdDoc "Internal IPv4 of portunus instance. This is used in the addToHosts option.";
};
internalIp6 = lib.mkOption {
type = with lib.types; nullOr str;
default = null;
description = lib.mdDoc "Internal IPv6 of portunus instance. This is used in the addToHosts option.";
};
ldapPreset = lib.mkOption {
type = lib.types.bool;
default = false;
description = lib.mdDoc "Whether to set config.security.ldap to portunus specific settings.";
};
removeAddGroup = lib.mkOption {
type = lib.types.bool;
default = false;
description = lib.mdDoc "When enabled, remove the function to add new Groups via the web ui, to enforce seeding usage.";
};
seedGroups = lib.mkOption {
type = lib.types.bool;
default = false;
description = lib.mdDoc "Wether to seed groups configured in services as not member managed groups.";
};
# TODO: upstream to nixos
seedSettings = lib.mkOption {
type = with lib.types; nullOr (attrsOf (listOf (attrsOf anything)));
default = null;
description = lib.mdDoc ''
Seed settings for users and grousp.
See upstream for format <https://github.com/majewsky/portunus#seeding-users-and-groups-from-static-configuration>
'';
};
};
config = {
assertions = [
{
assertion = cfg.configureOAuth2Proxy -> config.services.oauth2_proxy.keyFile != null;
message = ''
Setting services.portunus.configureOAuth2Proxy to true requires to set service.oauth2_proxy.keyFile
to a file that contains `OAUTH2_PROXY_CLIENT_SECRET` and `OAUTH2_PROXY_COOKIE_SECRET`.
'';
}
];
networking.hosts = lib.mkIf cfg.addToHosts {
${cfg.internalIp4} = [ cfg.domain ];
${cfg.internalIp6} = [ cfg.domain ];
};
nixpkgs.overlays = lib.mkIf cfg.enable [
(final: prev: with final; {
dex-oidc = prev.dex-oidc.override {
buildGoModule = args: buildGoModule (args // {
patches = args.patches or [ ] ++ [
# remember session
(fetchpatch {
url = "https://github.com/SuperSandro2000/dex/commit/d2fb6cdf8188e6973721ddac657a7c5d3daf6955.patch";
hash = "sha256-PKC7jsNyFN28qFZ7SLYgnd0s09G2cb+vBeFvRzyyLGQ=";
})
# Complain if the env set in SecretEnv cannot be found
(fetchpatch {
url = "https://github.com/dexidp/dex/commit/f25f72053c9282cfe22521cd508698a07dc5190f.patch";
hash = "sha256-dyo+UPpceHxL3gcBQaGaDAHJqmysDJw051gMG1aeh5o=";
})
];
vendorHash = "sha256-YIi67pPIcVndIjWk94ckv6X4WLELUe/J/03e+XWIdHE=";
});
};
portunus = (prev.portunus.override { buildGoModule = buildGo121Module; }).overrideAttrs ({ patches ? [ ], buildInputs ? [ ], ... }: let
version = "2.0.0-beta.2";
in {
inherit version;
# TODO: upstream
src = fetchFromGitHub {
owner = "majewsky";
repo = "portunus";
rev = "v${version}";
hash = "sha256-1OU3bepvqriGCW1qDszPnUDJ6eqBzNTiBZ2J4KF4ynw=";
};
patches = patches
++ lib.optional cfg.removeAddGroup ./portunus-remove-add-group.diff;
# TODO: upstream
buildInputs = buildInputs ++ [
libxcrypt-legacy
];
});
})
];
services = let
callbackURL = "https://${cfg.domain}/oauth2/callback";
clientID = "oauth2_proxy"; # - is not allowed in environment variables
in {
dex = {
enable = lib.mkIf cfg.configureOAuth2Proxy true;
# the user has no other option to accept this and all clients are internal anyway
settings.oauth2.skipApprovalScreen = true;
};
oauth2_proxy = lib.mkIf cfg.configureOAuth2Proxy {
enable = true;
inherit clientID;
nginx = {
inherit (config.services.portunus) domain;
};
provider = "oidc";
redirectURL = callbackURL;
reverseProxy = true;
upstream = "http://127.0.0.1:4181";
extraConfig = {
oidc-issuer-url = config.services.dex.settings.issuer;
provider-display-name = "Portunus";
};
};
portunus = {
dex.oidcClients = lib.mkIf cfg.configureOAuth2Proxy [{
inherit callbackURL;
id = clientID;
}];
seedPath = pkgs.writeText "seed.json" (builtins.toJSON cfg.seedSettings);
};
};
security.ldap = lib.mkIf cfg.ldapPreset {
domainName = cfg.domain;
givenNameField = "givenName";
groupFilter = group: "(&(objectclass=person)(isMemberOf=cn=${group},${ldap.roleBaseDN}))";
mailField = "mail";
port = 636;
roleBaseDN = "ou=groups";
roleField = "cn";
roleFilter = "(&(objectclass=groupOfNames)(member=%s))";
roleValue = "dn";
searchFilterWithGroupFilter = userFilterGroup: userFilter: if (userFilterGroup != null) then "(&${ldap.groupFilter userFilterGroup}${userFilter})" else userFilter;
sshPublicKeyField = "sshPublicKey";
searchUID = "search";
surnameField = "sn";
userField = "uid";
userFilter = replaceStr: "(&(objectclass=person)(|(uid=${replaceStr})(mail=${replaceStr})))";
userBaseDN = "ou=users";
};
};
}

View File

@ -1,96 +0,0 @@
{ config, lib, libS, pkgs, ... }:
# NOTE: requires https://github.com/NixOS/nixpkgs/pull/257503 because of new usage of extraPlugins
let
cfg = config.services.postgresql;
cfgu = config.services.postgresql.upgrade;
in
{
options.services.postgresql = {
upgrade = {
enable = libS.mkOpinionatedOption "install the upgrade-pg-cluster script to update postgres.";
extraArgs = lib.mkOption {
type = with lib.types; listOf str;
default = [ "--link" "--jobs=$(nproc)" ];
description = lib.mdDoc "Extra arguments to pass to pg_upgrade. See https://www.postgresql.org/docs/current/pgupgrade.html for doc.";
};
newPackage = (lib.mkPackageOptionMD pkgs "postgresql" {
default = [ "postgresql_16" ];
}) // {
description = lib.mdDoc ''
The postgres package to which should be updated.
After running upgrade-pg-cluster this must be set to services.postgresql.package to complete the update.
'';
};
stopServices = lib.mkOption {
type = with lib.types; listOf str;
default = [ ];
example = [ "hedgedoc" "hydra" "nginx" ];
description = lib.mdDoc "Systemd services to stop when upgrade is started.";
};
};
recommendedDefaults = libS.mkOpinionatedOption "set recommended default settings";
};
config = lib.mkIf cfg.enable {
environment.systemPackages = lib.optional cfgu.enable (
let
# conditions copied from nixos/modules/services/databases/postgresql.nix
newPackage = if cfg.enableJIT && !cfgu.newPackage.jitSupport then cfgu.newPackage.withJIT else cfg.newPackage;
newData = "/var/lib/postgresql/${cfgu.newPackage.psqlSchema}";
newBin = "${if cfg.extraPlugins == [] then cfgu.newPackage else cfgu.newPackage.withPackages cfg.extraPlugins}/bin";
oldPackage = if cfg.enableJIT && !cfg.package.jitSupport then cfg.package.withJIT else cfg.package;
oldData = config.services.postgresql.dataDir;
oldBin = "${if cfg.extraPlugins == [] then oldPackage else oldPackage.withPackages cfg.extraPlugins}/bin";
in
pkgs.writeScriptBin "upgrade-pg-cluster" /* bash */ ''
set -eu
echo "Current version: ${cfg.package.version}"
echo "Update version: ${cfgu.newPackage.version}"
if [[ ${cfgu.newPackage.version} == ${cfg.package.version} ]]; then
echo "There is no major postgres update available."
exit 2
fi
systemctl stop postgresql ${lib.concatStringsSep " " cfgu.stopServices}
install -d -m 0700 -o postgres -g postgres "${newData}"
cd "${newData}"
sudo -u postgres "${newBin}/initdb" -D "${newData}"
sudo -u postgres "${newBin}/pg_upgrade" \
--old-datadir "${oldData}" --new-datadir "${newData}" \
--old-bindir ${oldBin} --new-bindir ${newBin} \
${lib.concatStringsSep " " cfgu.extraArgs} \
"$@"
echo "
Run the following commands after setting:
services.postgresql.package = pkgs.postgresql_${lib.versions.major cfgu.newPackage.version}
sudo -u postgres vacuumdb --all --analyze-in-stages
${newData}/delete_old_cluster.sh
"
''
);
services = {
postgresql.enableJIT = lib.mkIf cfg.recommendedDefaults true;
postgresqlBackup = lib.mkIf cfg.recommendedDefaults {
compression = "zstd";
compressionLevel = 9;
pgdumpOptions = "--create --clean";
};
};
};
}

View File

@ -1,27 +0,0 @@
{ config, lib, libS, ... }:
let
cfg = config.simd;
in
{
options.simd = {
enable = lib.mkEnableOption "optimized builds with simd instructions";
arch = lib.mkOption {
type = with lib.types; nullOr str;
default = null;
description = ''
Microarchitecture string for nixpkgs.hostPlatform.gcc.march and to generate system-features.
Can be determined with: ``nix shell nixpkgs#gcc -c gcc -march=native -Q --help=target | grep march``
'';
};
};
config = {
nix.settings.system-features = lib.mkIf (cfg.arch != null) (libS.nix.gcc-system-features config.simd.arch);
nixpkgs.hostPlatform = lib.mkIf cfg.enable {
gcc.arch = config.simd.arch;
inherit (config.nixpkgs) system;
};
};
}

View File

@ -1,23 +0,0 @@
{ config, lib, libS, ... }:
let
cfg = config.slim;
in
{
options.slim = {
enable = libS.mkOpinionatedOption "disable some usual rarely used things to slim down the system";
};
config = lib.mkIf cfg.enable {
documentation = {
# html docs and info are not required, man pages are enough
doc.enable = false;
info.enable = false;
};
environment.defaultPackages = lib.mkForce [ ];
# durring testing only 550K-650K of the tmpfs where used
security.wrapperDirSize = "10M";
};
}

View File

@ -1,66 +0,0 @@
{ config, lib, libS, ... }:
let
cfgP = config.programs.ssh;
cfgS = config.services.openssh;
in
{
options = {
programs.ssh = {
addPopularKnownHosts = libS.mkOpinionatedOption "add ssh public keys of popular websites to known_hosts";
recommendedDefaults = libS.mkOpinionatedOption "set recommend and secure default settings";
};
services.openssh = {
fixPermissions = libS.mkOpinionatedOption "fix host key permissions to prevent lock outs";
};
};
config = lib.mkIf cfgP.addPopularKnownHosts {
programs.ssh = {
extraConfig = lib.mkIf cfgP.recommendedDefaults ''
# hard complain about wrong knownHosts
StrictHostKeyChecking accept-new
# make automated host key rotation possible
UpdateHostKeys yes
# fetch host keys via DNS and trust them
VerifyHostKeyDNS yes
'';
knownHosts = lib.mkMerge [
(libS.mkPubKey "github.com" "ssh-rsa" "AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=")
(libS.mkPubKey "github.com" "ecdsa-sha2-nistp256" "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=")
(libS.mkPubKey "github.com" "ssh-ed25519" "AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl")
(libS.mkPubKey "gitlab.com" "ssh-rsa" "AAAAB3NzaC1yc2EAAAADAQABAAABAQCsj2bNKTBSpIYDEGk9KxsGh3mySTRgMtXL583qmBpzeQ+jqCMRgBqB98u3z++J1sKlXHWfM9dyhSevkMwSbhoR8XIq/U0tCNyokEi/ueaBMCvbcTHhO7FcwzY92WK4Yt0aGROY5qX2UKSeOvuP4D6TPqKF1onrSzH9bx9XUf2lEdWT/ia1NEKjunUqu1xOB/StKDHMoX4/OKyIzuS0q/T1zOATthvasJFoPrAjkohTyaDUz2LN5JoH839hViyEG82yB+MjcFV5MU3N1l1QL3cVUCh93xSaua1N85qivl+siMkPGbO5xR/En4iEY6K2XPASUEMaieWVNTRCtJ4S8H+9")
(libS.mkPubKey "gitlab.com" "ecdsa-sha2-nistp256" "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFSMqzJeV9rUzU4kWitGjeR4PWSa29SPqJ1fVkhtj3Hw9xjLVXVYrU9QlYWrOLXBpQ6KWjbjTDTdDkoohFzgbEY=")
(libS.mkPubKey "gitlab.com" "ssh-ed25519" "AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjquxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf")
(libS.mkPubKey "git.openwrt.org" "ssh-rsa" "AAAAB3NzaC1yc2EAAAABIwAAAQEAtnM1w/A1uRZqZuYHhw4ASOe9mr3J2qKAa9K9zR8jG+B+NQVtYlIBSkmCFyP6OuydCmoRZ5Gs1I9pl/hEyi7ieEi6g9yww/JbV322cw04Tli46enIYDG1bnSxF6Qt4aXqvPhcObI3z/1Z3XR6weS1fiLDzLvzq+w1gNM77xExD4Mh27LTPkdwOWjkGa5joNx3EQUC3rzwxUqE4fhOT2Ii93h8FSAUXY9C32jkJj9x7vfaJEsCacs6YTiUKKxyzEB+TvFZdUtGtoRThX7UVICUCD2th/r3UeSp8ItWPg/KqzSg2pRfWeYszlVoD59JZ6YCupSjjRqZddghQc94Hev7oQ==")
(libS.mkPubKey "git.openwrt.org" "ecdsa-sha2-nistp256" "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBASOHg+tghASiZF0ClxYb/HEhUcqnD43I86YatRZSUsXNWLEd8yOzjOJExDHHaKtmZtQ/jfEMmoYbCjdEDOYm5g=")
(libS.mkPubKey "git.openwrt.org" "ssh-ed25519" "AAAAC3NzaC1lZDI1NTE5AAAAIJZFpKQMaLM8bG9lAPfEpTBExrzuiTKMni7PgktmDbJe")
(libS.mkPubKey "git.sr.ht" "ssh-rsa" "AAAAB3NzaC1yc2EAAAADAQABAAABAQDZ+l/lvYmaeOAPeijHL8d4794Am0MOvmXPyvHTtrqvgmvCJB8pen/qkQX2S1fgl9VkMGSNxbp7NF7HmKgs5ajTGV9mB5A5zq+161lcp5+f1qmn3Dp1MWKp/AzejWXKW+dwPBd3kkudDBA1fa3uK6g1gK5nLw3qcuv/V4emX9zv3P2ZNlq9XRvBxGY2KzaCyCXVkL48RVTTJJnYbVdRuq8/jQkDRA8lHvGvKI+jqnljmZi2aIrK9OGT2gkCtfyTw2GvNDV6aZ0bEza7nDLU/I+xmByAOO79R1Uk4EYCvSc1WXDZqhiuO2sZRmVxa0pQSBDn1DB3rpvqPYW+UvKB3SOz")
(libS.mkPubKey "git.sr.ht" "ecdsa-sha2-nistp256" "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCj6y+cJlqK3BHZRLZuM+KP2zGPrh4H66DacfliU1E2DHAd1GGwF4g1jwu3L8gOZUTIvUptqWTkmglpYhFp4Iy4=")
(libS.mkPubKey "git.sr.ht" "ssh-ed25519" "AAAAC3NzaC1lZDI1NTE5AAAAIMZvRd4EtM7R+IHVMWmDkVU3VLQTSwQDSAvW0t2Tkj60")
];
};
systemd.tmpfiles.rules = lib.mkIf cfgS.fixPermissions [
"d /etc 0755 root root -"
"d /etc/ssh 0755 root root -"
"f /etc/ssh/ssh_host_ed25519_key 0700 root root -"
"f /etc/ssh/ssh_host_ed25519_key.pub 0744 root root -"
"f /etc/ssh/ssh_host_rsa_key 0700 root root -"
"f /etc/ssh/ssh_host_rsa_key.pub 0744 root root -"
];
services.openssh.banner = ''
Welcome to another
Server :o
'';
};
}

View File

@ -1,31 +0,0 @@
{ config, lib, libS, ... }:
let
cfg = config.programs.tmux;
in
{
options = {
programs.tmux.recommendedDefaults = libS.mkOpinionatedOption "set recommended default settings";
};
config = lib.mkIf cfg.recommendedDefaults {
programs.tmux = {
keyMode = "vi";
shortcut = "Space";
aggressiveResize = true;
baseIndex = 1;
clock24 = true;
escapeTime = 100;
terminal = "xterm-256color";
extraConfig = ''
# focus events enabled for terminals that support them
set -g focus-events on
# open new tab in PWD
bind '"' split-window -c "#{pane_current_path}"
bind % split-window -h -c "#{pane_current_path}"
bind c new-window -c "#{pane_current_path}"
'';
};
};
}

View File

@ -1,97 +0,0 @@
{ config, lib, libS, ... }:
let
cfg = config.services.vaultwarden;
usingPostgres = cfg.dbBackend == "postgresql";
in
{
options = {
services.vaultwarden = {
configureNginx = libS.mkOpinionatedOption "configure nginx for the configured domain";
domain = lib.mkOption {
type = with lib.types; nullOr str;
default = null;
description = lib.mdDoc ''
The domain under which vaultwarden will be reachable.
'';
};
recommendedDefaults = libS.mkOpinionatedOption "set recommended default settings";
};
};
config = lib.mkIf cfg.enable {
assertions = [ {
assertion = cfg.configureNginx -> cfg.domain != null;
message = ''
Setting services.vaultwarden.configureNginx to true requires configuring services.vaultwarden.domain!
'';
} ];
nixpkgs.overlays = lib.mkIf cfg.recommendedDefaults [
(final: prev: {
vaultwarden = prev.vaultwarden.overrideAttrs ({ patches ? [], ... }: {
patches = patches ++ [
# add eu region push support
(final.fetchpatch {
url = "https://github.com/dani-garcia/vaultwarden/pull/3752.diff";
hash = "sha256-QWbuUotNss1TkIIW6c54Y7U7u2yLg2xHopEngtNawcc=";
})
];
});
})
];
services = {
nginx = lib.mkIf cfg.configureNginx {
upstreams.vaultwarden.servers."127.0.0.1:${toString config.services.vaultwarden.config.ROCKET_PORT}" = { };
virtualHosts.${cfg.domain}.locations = {
"/".proxyPass = "http://vaultwarden";
"/notifications/hub" = {
proxyPass = "http://vaultwarden";
proxyWebsockets = true;
};
};
};
postgresql = lib.mkIf usingPostgres {
enable = true;
ensureDatabases = [ "vaultwarden" ];
ensureUsers = [{
name = "vaultwarden";
ensureDBOwnership = true;
}];
};
vaultwarden.config = lib.mkMerge [
{
DATABASE_URL = lib.mkIf usingPostgres "postgresql:///vaultwarden?host=/run/postgresql";
DOMAIN = lib.mkIf (cfg.domain != null) "https://${cfg.domain}";
}
(lib.mkIf cfg.recommendedDefaults {
DATA_FOLDER = "/var/lib/vaultwarden"; # changes data directory
# TODO: change with 1.31.0 update
# ENABLE_WEBSOCKET = true;
LOG_LEVEL = "warn";
PASSWORD_ITERATIONS = 600000;
ROCKET_ADDRESS = "127.0.0.1";
ROCKET_PORT = lib.mkDefault 8222;
SIGNUPS_VERIFY = true;
TRASH_AUTO_DELETE_DAYS = 30;
WEBSOCKET_ADDRESS = "127.0.0.1";
WEBSOCKET_ENABLED = true;
WEBSOCKET_PORT = lib.mkDefault 8223;
})
];
};
systemd.services.vaultwarden = {
after = lib.mkIf usingPostgres [ "postgresql.service" ];
requires = lib.mkIf usingPostgres [ "postgresql.service" ];
serviceConfig = lib.mkIf cfg.recommendedDefaults {
StateDirectory = lib.mkForce "vaultwarden"; # modules defaults to bitwarden_rs
};
};
};
}

View File

@ -1,34 +0,0 @@
{ config, lib, libS, options, pkgs, ... }:
let
cfg = config.boot.zfs;
in
{
options = {
boot.zfs = {
recommendedDefaults = libS.mkOpinionatedOption "enable recommended ZFS settings";
latestCompatibleKernel = libS.mkOpinionatedOption "use the latest ZFS compatible kernel";
};
};
config = lib.mkIf cfg.enabled {
boot.kernelPackages =
let
ver = config.boot.zfs.package.latestCompatibleLinuxPackages.kernel.version;
in
# 6.0 has a bug in the bind syscall and does not error correct when the port is already in use
# https://lists.fedoraproject.org/archives/list/devel@lists.fedoraproject.org/thread/7VPNMC77YC3SI5LFYKUA4B5MTFPLTLVB/
# https://lore.kernel.org/stable/CAFsF8vL4CGFzWMb38_XviiEgxoKX0GYup=JiUFXUOmagdk9CRg@mail.gmail.com/
lib.mkIf (cfg.latestCompatibleKernel && lib.versions.majorMinor ver != "6.0") (lib.mkDefault config.boot.zfs.package.latestCompatibleLinuxPackages);
services.zfs = lib.mkIf cfg.recommendedDefaults {
autoScrub.enable = true;
trim.enable = true;
};
virtualisation.containers.storage.settings = lib.mkIf cfg.recommendedDefaults (lib.recursiveUpdate options.virtualisation.containers.storage.settings.default {
# fixes: Error: 'overlay' is not supported over zfs, a mount_program is required: backing file system is unsupported for this graph driver
storage.options.mount_program = "${pkgs.fuse-overlayfs}/bin/fuse-overlayfs";
});
};
}

View File

@ -1,7 +1,5 @@
{ pkgs, lib, config, ... }:
let
in {
{ pkgs, ... }:
{
i18n = {
defaultLocale = "en_US.utf8";
supportedLocales = [
@ -10,7 +8,6 @@ in {
];
};
networking.firewall.allowedTCPPorts = [ 22 ];
services = {
@ -26,13 +23,6 @@ in {
};
};
users.users.brain = {
isNormalUser = true;
description = "Administrator";
extraGroups = [ "networkmanager" "wheel" ];
shell = pkgs.zsh;
};
nixpkgs.config.allowUnfree = true;
programs = {
@ -180,7 +170,7 @@ in {
options = "--delete-oder-than 14d";
};
diff-system = true;
diffSystem = true;
};
system = {

View File

@ -1,6 +1,5 @@
{ pkgs, lib, config, ... }:
let
in {
{ pkgs, ... }:
{
time.timeZone = "America/New_York";
console.keyMap = "us";

View File

@ -1,27 +1,8 @@
{
pkgs,
lib,
config,
}: let
pubKeys = import ./keys/default.nix;
in {
isNormalUser = true;
description = "AmethystAndroid";
uid = 1000;
extraGroups = [
"wheel"
"media"
(lib.mkIf config.networking.networkmanager.enable "networkmanager")
(lib.mkIf config.programs.adb.enable "adbusers")
(lib.mkIf config.programs.wireshark.enable "wireshark")
(lib.mkIf config.programs.virtualisation.docker.enable "docker")
"libvirtd"
"dialout"
"plugdev"
"uaccess"
];
shell = pkgs.fish;
openssh.authorizedKeys.keys = [
(lib.mkIf (pubKeys ? ${config.networking.hostName}) pubKeys.${config.networking.hostName})
];
{ pkgs, lib, config }:
import ../default.nix {
inherit pkgs lib config;
userName = "AmethystAndroid";
pubKeys = {
palatine-hill = "ed25516-AAAAAAA";
};
}

View File

@ -1,3 +0,0 @@
{
palatine-hill = "ed25516-AAAAAAA";
}

21
users/default.nix Normal file
View File

@ -0,0 +1,21 @@
{ lib, config, pkgs, userName, pubKeys }:
{
isNormalUser = true;
uid = 1000;
extraGroups = [
"wheel"
"media"
(lib.mkIf config.networking.networkmanager.enable "networkmanager")
(lib.mkIf config.programs.adb.enable "adbusers")
(lib.mkIf config.programs.wireshark.enable "wireshark")
(lib.mkIf config.programs.virtualisation.docker.enable "docker")
"libvirtd"
"dialout"
"plugdev"
"uaccess"
];
shell = pkgs.zsh;
openssh.authorizedKeys.keys = [
(lib.mkIf (pubKeys ? ${config.networking.hostName}) pubKeys.${config.networking.hostName})
];
}

View File