Compare commits

..

62 Commits

Author SHA1 Message Date
h7x4 f4b1f090e4 flake.lock: bump various 2026-06-30 04:15:37 +09:00
h7x4 7c684e42f0 treewide: fix rsync <-> rrsync communication 2026-06-30 02:41:40 +09:00
h7x4 90bfda9066 kommode/gitea: bump AVATAR_MAX_ORIGIN_SIZE from 2MB to 4MB 2026-06-24 14:33:20 +09:00
h7x4 522d8f18cb flake.{nix,lock}: bump roowho2 2026-06-24 13:38:03 +09:00
h7x4 5e613a03fc treewide: set relatime for most root mounts 2026-06-23 01:12:16 +09:00
h7x4 170fb2a980 bicep/synapse: fix dbname option 2026-06-22 18:55:14 +09:00
h7x4 3e627472e9 flake.{nix,lock}: bump matrix-next 2026-06-22 18:55:13 +09:00
Adrian G L e05c4ed8ca feat: add initialdeploy hashed password to root 2026-06-21 18:24:01 +02:00
h7x4 3fee83ec05 ildkule/loki: restrict incoming connections to pvv + ntnu 2026-06-22 01:23:16 +09:00
h7x4 a1f02fc39d {ildkule/loki,base/fluentbit}: send data over https 2026-06-22 01:23:16 +09:00
Adrian G L 6e37635aac ildkule/loki: firewall all endpoints except push API
Co-authored-by: Øystein Kristoffer Tveit <oysteikt@pvv.ntnu.no>
2026-06-22 01:23:14 +09:00
h7x4 cdc3ad488b bicep/postgres: add script for updating all collations 2026-06-22 01:12:59 +09:00
h7x4 aa2712005a temmie/nfs-mounts: create by-uid bindmounts 2026-06-17 13:43:19 +09:00
h7x4 89921b533b temmie/userweb: further harden log-processor 2026-06-17 12:31:02 +09:00
h7x4 75f87ffab8 temmie/userweb: run passwd sync in different unit 2026-06-17 12:15:23 +09:00
h7x4 b910cf9563 temmie/userweb: suppress erroneous access log for documentRoot 2026-06-17 08:57:55 +09:00
h7x4 d23adbd4c2 temmie/userweb: deny access to documentRoot 2026-06-17 08:49:44 +09:00
h7x4 48c0a4e504 temmie/userweb: fix directory denylist enforcement 2026-06-17 08:23:08 +09:00
h7x4 374d9b1bc7 flake.nix: passthru machine config, pkgs and config.system.build
This shortens down the path needed to build both overlayed packages and
all the other machine derivations. Here are some examples:

```
nix build .#machine.etc
nix build '.#machine.units."nginx.service".unit'
nix build .#machine.pkgs.overlayed-package
nix build .#machine.config.services.nginx.package
```
2026-06-17 08:10:17 +09:00
h7x4 d84cc73819 temmie/userweb: handle more .php\d suffixes 2026-06-16 19:07:58 +09:00
h7x4 b738f08c09 temmie/userweb: render path denylist into Directory/Files directives 2026-06-16 19:07:57 +09:00
h7x4 8252bba3ad temmie/userweb: enable httpd trace on debugMode 2026-06-16 19:07:57 +09:00
h7x4 a776a5a5fe temmie/userweb: explicitly override mod_perl and mod_userdir 2026-06-16 19:07:57 +09:00
h7x4 ed57744ec3 temmie/userweb: add more patterns to denylist 2026-06-16 16:07:32 +09:00
h7x4 226db1f46e temmie/userweb: add more DirectoryIndex variants 2026-06-16 16:07:32 +09:00
h7x4 51e1656177 temmie/userweb: disable ~pvv 2026-06-16 15:53:52 +09:00
h7x4 47d2dcf9ff temmie/userweb: add bro server to userweb slice 2026-06-16 03:37:28 +09:00
h7x4 254b1d9b14 temmie/userweb: split into more modules 2026-06-16 03:33:28 +09:00
h7x4 2301672a21 temmie/userweb: run log processors as separate systemd units
This lets us divide up some of the logic making httpd itself less
brittle, and also reduces the amount of privileges for httpd.
2026-06-16 02:56:28 +09:00
felixalb 7145abadf3 flake: update input pvv-nettsiden 2026-06-13 16:59:50 +02:00
h7x4 b533b09c8f base/various: add to slice system-monitoring 2026-06-13 04:45:39 +09:00
h7x4 526b55c49a {ildkule/prometheus,base}: send stats over HTTPS through nginx 2026-06-13 02:54:28 +09:00
h7x4 e80189c6eb temmie/userweb: stop cating passwd on startup 2026-06-13 01:41:05 +09:00
h7x4 56a51e4c6f temmie/userweb: mount homedirs under /amd 2026-06-13 01:39:20 +09:00
h7x4 f54109f6f3 temmie/userweb: set handlers for php and perl scripts 2026-06-13 01:26:27 +09:00
Vegard Bieker Matthey 5763a76136 user/vegardbm: change shell to zsh and add ssh key 2026-06-08 11:35:44 +02:00
h7x4 b57a935b4c base/rsyslogd: init 2026-06-08 12:58:37 +09:00
h7x4 b4582a160f skrot/dibbler: rotate database password 2026-06-07 17:58:33 +09:00
h7x4 ac094d350d base/timesyncd: specify ntp servers 2026-06-07 17:52:54 +09:00
h7x4 b848e0f1cc temmie/userweb: add log processor for apache 2026-06-07 06:03:18 +09:00
h7x4 c671329b93 temmie/userweb: inject users from passwd into httpd sandbox 2026-06-07 05:28:24 +09:00
Vegard Bieker Matthey e6a3d43493 modules/drumknotty: use correct screen window name for dibbler 2026-06-05 22:14:02 +02:00
h7x4 cafc95db8f bicep/mjolnir: use nodejs v22 2026-06-06 04:43:58 +09:00
h7x4 2d6b09cb32 bikkje: label ports in firewall port list 2026-06-06 04:08:16 +09:00
h7x4 ce0af2f6e4 flake.nix: add app for building gitea workflows locally 2026-06-06 04:05:26 +09:00
h7x4 88892115b5 base: enable autoScrub for all btrfs machine by default 2026-06-06 04:05:26 +09:00
h7x4 8a290d30e7 modules/drumknotty: split into several parts
This also fixes a few issues, such as enabling `createLocalDatabase` for
multiple programs, and wraps all the screen logic within a screenrc
file. Some assertions were also added to avoid some easy-to-make
mistakes.
2026-06-05 14:21:35 +02:00
Vegard Bieker Matthey 3197c6a5e3 attach with dibbler window selected 2026-06-05 14:21:35 +02:00
Vegard Bieker Matthey f8dcaddefb use main branch for worblehat after merge 2026-06-05 14:21:28 +02:00
Vegard Bieker Matthey 009d89f959 set default settings for worblehat and dibbler 2026-06-05 14:09:06 +02:00
Vegard Bieker Matthey 21bba3ec7e add worblehat daemons 2026-06-05 14:09:06 +02:00
Vegard Bieker Matthey 9552351776 add database password for worblehat 2026-06-05 14:09:06 +02:00
Vegard Bieker Matthey 7e754ade71 drumknotty: init 2026-06-05 14:08:58 +02:00
h7x4 fcd81aed00 packages/ooye: 3.5.1 -> 3.6.0 2026-06-04 19:17:29 +09:00
h7x4 966081ebfc bicep/mysql: enable userstat 2026-06-03 15:31:27 +09:00
h7x4 39d313579c bicep/mysql: rotate slow query logs 2026-06-03 15:21:18 +09:00
h7x4 3386153b8b ildkule/prometheus/exim: make scheme explicit 2026-06-03 13:35:13 +09:00
h7x4 56906241f6 bekkalokk/roundcube: temporary fix for webmail redirects 2026-06-01 03:52:09 +09:00
h7x4 3fe71d21f6 bekkalokk/roundcube: webdir moved to public_html within package 2026-06-01 02:57:43 +09:00
h7x4 074d240595 base: tag generation as auto if built by auto upgrade service 2026-06-01 01:00:50 +09:00
h7x4 1ce3372683 lupine/binfmt: enable 2026-06-01 01:00:50 +09:00
Adrian G L 5f14c15679 feat: add radicale to bekkalokk 2026-06-01 00:59:54 +09:00
60 changed files with 2490 additions and 706 deletions
+16 -1
View File
@@ -1,4 +1,5 @@
{
config,
pkgs,
lib,
inputs,
@@ -13,7 +14,6 @@
./mitigations.nix
./flake-input-exporter.nix
./hardening.nix
./networking.nix
./nix.nix
@@ -33,12 +33,15 @@
./services/openssh.nix
./services/polkit.nix
./services/postfix.nix
./services/prometheus-flake-input-exporter.nix
./services/prometheus-node-exporter.nix
./services/prometheus-systemd-exporter.nix
./services/roowho2.nix
./services/rsyslogd.nix
./services/scrutiny-collector.nix
./services/smartd.nix
./services/thermald.nix
./services/timesyncd.nix
./services/uptimed.nix
./services/userborn.nix
./services/userdbd.nix
@@ -46,12 +49,18 @@
system.nixos.tags = lib.optionals (inputs.self.sourceInfo ? dirtyRev) [ "dirty" ];
specialisation."auto-upgrade".configuration = {
system.nixos.tags = [ "auto" ];
};
boot.tmp.cleanOnBoot = lib.mkDefault true;
boot.kernelPackages = lib.mkDefault pkgs.linuxPackages_latest;
boot.loader.systemd-boot.enable = lib.mkDefault true;
boot.loader.efi.canTouchEfiVariables = lib.mkDefault true;
services.btrfs.autoScrub.enable = lib.mkDefault (lib.any ({ fsType, ... }: fsType == "btrfs") (lib.attrValues config.fileSystems));
time.timeZone = "Europe/Oslo";
i18n.defaultLocale = "en_US.UTF-8";
@@ -86,8 +95,14 @@
AllowHibernation = lib.mkDefault false;
};
systemd.slices."system-monitoring" = {
description = "Monitoring related services";
};
# users.mutableUsers = lib.mkDefault false;
users.users.root.initialHashedPassword = "$y$j9T$ahP6GAdttD17OMBo7Yqeh.$Ad7qBcFvTL7HrJ9uTtrQzksN3220Nj9t/CrP6DwgK34"; # generated using mkpasswd, see huttiheita root on vaultwarden
users.groups."drift".name = "drift";
# Trusted users on the nix builder machines
-55
View File
@@ -1,55 +0,0 @@
{
config,
inputs,
lib,
pkgs,
values,
...
}:
let
data = lib.flip lib.mapAttrs inputs (
name: input: {
inherit (input)
lastModified
;
}
);
folder = pkgs.writeTextDir "share/flake-inputs" (
lib.concatMapStringsSep "\n" (
{ name, value }: ''nixos_last_modified_input{flake="${name}"} ${toString value.lastModified}''
) (lib.attrsToList data)
);
port = 9102;
in
{
services.nginx.virtualHosts."${config.networking.fqdn}-nixos-metrics" = {
serverName = config.networking.fqdn;
serverAliases = [
"${config.networking.hostName}.pvv.org"
];
locations."/metrics" = {
root = "${folder}/share";
tryFiles = "/flake-inputs =404";
extraConfig = ''
default_type text/plain;
'';
};
listen = [
{
inherit port;
addr = "0.0.0.0";
}
];
extraConfig = ''
allow ${values.hosts.ildkule.ipv4}/32;
allow ${values.hosts.ildkule.ipv6}/128;
allow 127.0.0.1/32;
allow ::1/128;
allow ${values.ipv4-space};
allow ${values.ipv6-space};
deny all;
'';
};
networking.firewall.allowedTCPPorts = [ port ];
}
-1
View File
@@ -8,6 +8,5 @@
# Let's not spam LetsEncrypt in `nixos-rebuild build-vm` mode:
virtualisation.vmVariant = {
security.acme.defaults.server = "https://127.0.0.1";
users.users.root.initialPassword = "root";
};
}
+1
View File
@@ -13,6 +13,7 @@ in
"--refresh"
"--no-write-lock-file"
"--specialisation auto-upgrade"
# --update-input is deprecated since nix 2.22, and removed in lix 2.90
# as such we instead use --override-input combined with --refresh
# https://git.lix.systems/lix-project/lix/issues/400
+5 -2
View File
@@ -62,8 +62,10 @@ in
name = "loki";
match = "*";
host = "ildkule.pvv.ntnu.no";
port = 3100;
host = "loki.pvv.ntnu.no";
port = 443;
tls = "on";
"tls.verify" = "on";
uri = "/loki/api/v1/push";
compress = "gzip";
@@ -88,6 +90,7 @@ in
systemd.services.fluent-bit = lib.mkIf cfg.enable {
serviceConfig = {
Slice = "system-monitoring.slice";
StateDirectory = "fluent-bit";
# NOTE: This hardening might be way too strong for general purpose use, don't upstream this.
+1
View File
@@ -14,6 +14,7 @@ in
};
systemd.services."systemd-journal-upload".serviceConfig = lib.mkIf cfg.enable {
Slice = "system-monitoring.slice";
IPAddressDeny = "any";
IPAddressAllow = [
values.hosts.ildkule.ipv4
+2 -24
View File
@@ -1,18 +1,5 @@
{ config, lib, ... }:
{
# nginx return 444 for all nonexistent virtualhosts
systemd.services.nginx.after = [ "generate-snakeoil-certs.service" ];
environment.snakeoil-certs = lib.mkIf config.services.nginx.enable {
"/etc/certs/nginx" = {
owner = "nginx";
group = "nginx";
};
};
networking.firewall.allowedTCPPorts = lib.mkIf config.services.nginx.enable [ 80 443 ];
services.nginx = {
recommendedTlsSettings = true;
recommendedProxySettings = true;
@@ -60,17 +47,8 @@
];
}
];
sslCertificate = "/etc/certs/nginx.crt";
sslCertificateKey = "/etc/certs/nginx.key";
addSSL = true;
extraConfig = "return 444;";
};
${config.networking.fqdn} = {
sslCertificate = lib.mkDefault "/etc/certs/nginx.crt";
sslCertificateKey = lib.mkDefault "/etc/certs/nginx.key";
addSSL = lib.mkDefault true;
extraConfig = lib.mkDefault "return 444;";
};
};
networking.firewall.allowedTCPPorts = lib.mkIf config.services.nginx.enable [ 80 443 ];
}
@@ -0,0 +1,47 @@
{
config,
inputs,
lib,
pkgs,
values,
...
}:
let
data = lib.flip lib.mapAttrs inputs (
name: input: {
inherit (input)
lastModified
;
}
);
folder = pkgs.writeTextDir "share/flake-inputs" (
lib.concatMapStringsSep "\n" (
{ name, value }: ''nixos_last_modified_input{flake="${name}"} ${toString value.lastModified}''
) (lib.attrsToList data)
);
in
{
services.nginx = {
enable = lib.mkDefault true;
virtualHosts.${config.networking.fqdn} = lib.mkIf config.services.nginx.enable {
forceSSL = true;
enableACME = true;
kTLS = true;
locations."/prometheus-nixos-flake-input-exporter/metrics" = {
root = "${folder}/share";
tryFiles = "/flake-inputs =404";
extraConfig = ''
default_type text/plain;
allow 127.0.0.1;
allow ::1;
allow ${values.hosts.ildkule.ipv4};
allow ${values.hosts.ildkule.ipv6};
deny all;
'';
};
};
};
}
+24 -9
View File
@@ -5,19 +5,34 @@ in
{
services.prometheus.exporters.node = {
enable = lib.mkDefault true;
listenAddress = "127.0.0.1";
port = 9100;
enabledCollectors = [ "systemd" ];
};
systemd.services.prometheus-node-exporter.serviceConfig = lib.mkIf cfg.enable {
IPAddressDeny = "any";
IPAddressAllow = [
"127.0.0.1"
"::1"
values.hosts.ildkule.ipv4
values.hosts.ildkule.ipv6
];
services.nginx = lib.mkIf cfg.enable {
enable = lib.mkDefault true;
virtualHosts.${config.networking.fqdn} = lib.mkIf config.services.nginx.enable {
forceSSL = true;
enableACME = true;
kTLS = true;
locations."/prometheus-node-exporter/metrics" = {
proxyPass = "http://localhost:${toString cfg.port}/metrics";
extraConfig = ''
allow 127.0.0.1;
allow ::1;
allow ${values.hosts.ildkule.ipv4};
allow ${values.hosts.ildkule.ipv6};
deny all;
'';
};
};
};
networking.firewall.allowedTCPPorts = lib.mkIf cfg.enable [ cfg.port ];
systemd.services = lib.mkIf cfg.enable {
"prometheus-node-exporter".serviceConfig.Slice = "system-monitoring.slice";
};
}
+24 -9
View File
@@ -5,6 +5,7 @@ in
{
services.prometheus.exporters.systemd = {
enable = lib.mkDefault true;
listenAddress = "127.0.0.1";
port = 9101;
extraFlags = [
"--systemd.collector.enable-restart-count"
@@ -12,15 +13,29 @@ in
];
};
systemd.services.prometheus-systemd-exporter.serviceConfig = {
IPAddressDeny = "any";
IPAddressAllow = [
"127.0.0.1"
"::1"
values.hosts.ildkule.ipv4
values.hosts.ildkule.ipv6
];
services.nginx = lib.mkIf cfg.enable {
enable = lib.mkDefault true;
virtualHosts.${config.networking.fqdn} = lib.mkIf config.services.nginx.enable {
forceSSL = true;
enableACME = true;
kTLS = true;
locations."/prometheus-systemd-exporter/metrics" = {
proxyPass = "http://localhost:${toString cfg.port}/metrics";
extraConfig = ''
allow 127.0.0.1;
allow ::1;
allow ${values.hosts.ildkule.ipv4};
allow ${values.hosts.ildkule.ipv6};
deny all;
'';
};
};
};
networking.firewall.allowedTCPPorts = lib.mkIf cfg.enable [ cfg.port ];
systemd.services = lib.mkIf cfg.enable {
"prometheus-systemd-exporter".serviceConfig.Slice = "system-monitoring.slice";
};
}
+20
View File
@@ -0,0 +1,20 @@
{ config, lib, ... }:
let
cfg = config.services.rsyslogd;
in
{
services.rsyslogd = {
enable = lib.mkDefault true;
defaultConfig = ''
*.* @loghost.pvv.ntnu.no
'';
};
services.journald.extraConfig = lib.mkIf cfg.enable ''
ForwardToSyslog=yes
'';
systemd.services = lib.mkIf cfg.enable {
"syslog".serviceConfig.Slice = "system-monitoring.slice";
};
}
+12
View File
@@ -0,0 +1,12 @@
{ ... }:
{
services.timesyncd = {
servers = [ "ntp.ntnu.no" ];
fallbackServers = [
"0.pool.ntp.org"
"1.pool.ntp.org"
"0.no.pool.ntp.org"
];
};
}
+3 -1
View File
@@ -23,7 +23,7 @@ in
};
};
systemd.services.uptimed = lib.mkIf (cfg.enable) {
systemd.services.uptimed = lib.mkIf cfg.enable {
serviceConfig = let
uptimed = pkgs.uptimed.overrideAttrs (prev: {
postPatch = ''
@@ -35,6 +35,8 @@ in
});
in {
Slice = "system-monitoring.slice";
Type = "notify";
ExecStart = lib.mkForce "${uptimed}/sbin/uptimed -f";
Generated
+83 -39
View File
@@ -44,11 +44,11 @@
]
},
"locked": {
"lastModified": 1771267058,
"narHash": "sha256-EEL4SmD1b3BPJPsSJJ4wDTXWMumJqbR+BLzhJJG0skE=",
"lastModified": 1780236125,
"narHash": "sha256-LFFdHbXFthgU81S6P+p9FFKs/Wx868d4CtsFvbgvQqo=",
"ref": "main",
"rev": "e3962d02c78b9c7b4d18148d931a9a4bf22e7902",
"revCount": 254,
"rev": "267af00ebd8693632fa0f80300e49203cfcebbd4",
"revCount": 258,
"type": "git",
"url": "https://git.pvv.ntnu.no/Projects/dibbler.git"
},
@@ -126,10 +126,10 @@
"rust-overlay": "rust-overlay_2"
},
"locked": {
"lastModified": 1777019032,
"narHash": "sha256-29lw7THThWb5DW01rVRj1b816Apwz/P4m2wVWaSIadU=",
"lastModified": 1779345549,
"narHash": "sha256-naEMpFsQrco5eDtYmcTG6cpbPG+kqElOyS7fTb/jw9s=",
"ref": "main",
"rev": "55262afca46c96f75a834d4e00e30d5fb20affb6",
"rev": "5631e1124508fdef0604b08bfcf6343d967a21b3",
"revCount": 61,
"type": "git",
"url": "https://git.pvv.ntnu.no/Grzegorz/greg-ng.git"
@@ -161,6 +161,27 @@
"url": "https://git.pvv.ntnu.no/Grzegorz/grzegorz-clients.git"
}
},
"libdib": {
"inputs": {
"nixpkgs": [
"worblehat",
"nixpkgs"
]
},
"locked": {
"lastModified": 1780178524,
"narHash": "sha256-2PcNyNqbGCWBpAMdCU1HxSQmhQiG6evdjxVnPA7w5bQ=",
"ref": "refs/heads/main",
"rev": "2406de41ce9d0a1404cbf4e55537e3f720f37f23",
"revCount": 15,
"type": "git",
"url": "https://git.pvv.ntnu.no/Projects/libdib.git"
},
"original": {
"type": "git",
"url": "https://git.pvv.ntnu.no/Projects/libdib.git"
}
},
"matrix-next": {
"inputs": {
"nixpkgs": [
@@ -168,16 +189,16 @@
]
},
"locked": {
"lastModified": 1764844095,
"narHash": "sha256-Drf1orxsmFDzO+UbPo85gHjXW7QzAM+6oTPvI7vOSik=",
"lastModified": 1782122067,
"narHash": "sha256-95q3DiYOTHjQGbqR0I1w4ETrH+smtddqW0bBxaB/Egg=",
"owner": "dali99",
"repo": "nixos-matrix-modules",
"rev": "25b9f31ef1dbc3987b4c716de716239f2b283701",
"rev": "0e0fd9f6a407b08dd5e180a2ff6c3808461e2c47",
"type": "github"
},
"original": {
"owner": "dali99",
"ref": "v0.8.0",
"ref": "master",
"repo": "nixos-matrix-modules",
"type": "github"
}
@@ -270,11 +291,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1779622335,
"narHash": "sha256-06G98ieM6l+OI7EMhlvchgDBDn+DvIWCNj40LDhKpmc=",
"rev": "705e9929918b43bd7b715dc0a878ac870449bb03",
"lastModified": 1782708249,
"narHash": "sha256-ASIzbiHLqFDHfomCJVMDaW1rsD9IJQ/m997Tw8569tM=",
"rev": "68d68014342a18d2514d6e6f1185cfc24d02fc00",
"type": "tarball",
"url": "https://releases.nixos.org/nixos/26.05-small/nixos-26.05beta1.705e9929918b/nixexprs.tar.xz"
"url": "https://releases.nixos.org/nixos/26.05-small/nixos-26.05.3713.68d68014342a/nixexprs.tar.xz"
},
"original": {
"type": "tarball",
@@ -298,11 +319,11 @@
},
"nixpkgs-unstable": {
"locked": {
"lastModified": 1778586796,
"narHash": "sha256-XmDljcG4x8slQDlsWOc77pCA1YVuYn8JGumkYlhfTxI=",
"rev": "b25e938b89759b5f9466fc53c4a970244f84dc39",
"lastModified": 1782731455,
"narHash": "sha256-vFMOLxoARiCUBQOysAAJ0VmPzilmHUqk3EZfSRweKN8=",
"rev": "9c4c05a947a91dc14625265fab505fb695e93218",
"type": "tarball",
"url": "https://releases.nixos.org/nixos/unstable-small/nixos-26.05pre996582.b25e938b8975/nixexprs.tar.xz"
"url": "https://releases.nixos.org/nixos/unstable-small/nixos-26.11pre1024311.9c4c05a947a9/nixexprs.tar.xz"
},
"original": {
"type": "tarball",
@@ -316,11 +337,11 @@
]
},
"locked": {
"lastModified": 1780062186,
"narHash": "sha256-FSkwKO/56i9RddwSydK804fSnIvbczBnFJgr2/m+F9U=",
"lastModified": 1780764154,
"narHash": "sha256-Xvf9aBNLYDnbDKdtFjp5GEA/rZwVczHZWbJ0hac8Vv4=",
"ref": "main",
"rev": "db2b19f144af046161b7f9ca69ddaf3f06fcceea",
"revCount": 13,
"rev": "8b4541be73ee3bd6c60525b2f42605efe89398c9",
"revCount": 14,
"type": "git",
"url": "https://git.pvv.ntnu.no/Projects/passwd2systemd-users.git"
},
@@ -358,11 +379,11 @@
]
},
"locked": {
"lastModified": 1779903528,
"narHash": "sha256-4rajaHeBeQ4PjbNSpslE9G3A5mZM1J/64ls+VoufWZo=",
"lastModified": 1782759909,
"narHash": "sha256-gktjBeZyoRvVBkm2cO1tD99fdQ34iUDyB6iecRdorm4=",
"ref": "main",
"rev": "bba7413a1c611d4918fbef4d3aa55e465ca3f3fb",
"revCount": 585,
"rev": "ad6c79fb713884a4a2df8aab30914cd0c1c2e6cb",
"revCount": 587,
"type": "git",
"url": "https://git.pvv.ntnu.no/Projects/nettsiden.git"
},
@@ -413,7 +434,8 @@
"pvv-nettsiden": "pvv-nettsiden",
"qotd": "qotd",
"roowho2": "roowho2",
"sops-nix": "sops-nix"
"sops-nix": "sops-nix",
"worblehat": "worblehat"
}
},
"roowho2": {
@@ -425,17 +447,17 @@
"rust-overlay": "rust-overlay_4"
},
"locked": {
"lastModified": 1778600367,
"narHash": "sha256-YB0b2xUf4D8792D5Ay//7C3AjHyv+9yoy8K1mTe+wvE=",
"lastModified": 1782275838,
"narHash": "sha256-CW84hEFcypvEegQp+4zZZ4lCnPT7Qn27OpKiQBxiWS8=",
"ref": "main",
"rev": "8e5f2849ff7c9616100fe928261512a7ad647939",
"revCount": 91,
"rev": "71d2b72c34352a79dbee8ebf23ce64f39aead692",
"revCount": 102,
"type": "git",
"url": "https://git.pvv.ntnu.no/Projects/roowho2.git"
},
"original": {
"ref": "main",
"rev": "8e5f2849ff7c9616100fe928261512a7ad647939",
"rev": "71d2b72c34352a79dbee8ebf23ce64f39aead692",
"type": "git",
"url": "https://git.pvv.ntnu.no/Projects/roowho2.git"
}
@@ -469,11 +491,11 @@
]
},
"locked": {
"lastModified": 1777000482,
"narHash": "sha256-CZ5FKUSA8FCJf0h9GWdPJXoVVDL9H5yC74GkVc5ubIM=",
"lastModified": 1779333539,
"narHash": "sha256-lpmN2lrBDZDPjov2cbD3bOOJsI0fkKolKXasYPCqSys=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "403c09094a877e6c4816462d00b1a56ff8198e06",
"rev": "672fa5fc5608d5cd82286a6f69aaf84a40b4fe41",
"type": "github"
},
"original": {
@@ -531,11 +553,11 @@
]
},
"locked": {
"lastModified": 1777944972,
"narHash": "sha256-VfGRo1qTBKOe3s2gOv8LSoA6Fk19PvBlwQ1ECN0Evn8=",
"lastModified": 1782165805,
"narHash": "sha256-478kKQBvK6SYTOdN2h9jhKJv94nbXRbFMfuL1WshErg=",
"owner": "Mic92",
"repo": "sops-nix",
"rev": "c591bf665727040c6cc5cb409079acb22dcce33c",
"rev": "56b24064fdcaedca53553b1a6d607fd23b613a24",
"type": "github"
},
"original": {
@@ -544,6 +566,28 @@
"repo": "sops-nix",
"type": "github"
}
},
"worblehat": {
"inputs": {
"libdib": "libdib",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1780762731,
"narHash": "sha256-EuDaLnasWN0mpi995n+fAQQfPGBhqNW4fNjlQRpHt58=",
"ref": "main",
"rev": "931bd2d63e285e767c8c81c103dda3c0a63f2965",
"revCount": 108,
"type": "git",
"url": "https://git.pvv.ntnu.no/Projects/worblehat.git"
},
"original": {
"ref": "main",
"type": "git",
"url": "https://git.pvv.ntnu.no/Projects/worblehat.git"
}
}
},
"root": "root",
+36 -5
View File
@@ -23,7 +23,10 @@
dibbler.url = "git+https://git.pvv.ntnu.no/Projects/dibbler.git?ref=main";
dibbler.inputs.nixpkgs.follows = "nixpkgs";
matrix-next.url = "github:dali99/nixos-matrix-modules/v0.8.0";
worblehat.url = "git+https://git.pvv.ntnu.no/Projects/worblehat.git?ref=main";
worblehat.inputs.nixpkgs.follows = "nixpkgs";
matrix-next.url = "github:dali99/nixos-matrix-modules/master";
matrix-next.inputs.nixpkgs.follows = "nixpkgs";
nix-gitea-themes.url = "git+https://git.pvv.ntnu.no/Drift/nix-gitea-themes.git?ref=main";
@@ -32,7 +35,7 @@
minecraft-heatmap.url = "git+https://git.pvv.ntnu.no/Projects/minecraft-heatmap.git?ref=main";
minecraft-heatmap.inputs.nixpkgs.follows = "nixpkgs";
roowho2.url = "git+https://git.pvv.ntnu.no/Projects/roowho2.git?ref=main&rev=8e5f2849ff7c9616100fe928261512a7ad647939";
roowho2.url = "git+https://git.pvv.ntnu.no/Projects/roowho2.git?ref=main&rev=71d2b72c34352a79dbee8ebf23ce64f39aead692";
roowho2.inputs.nixpkgs.follows = "nixpkgs";
greg-ng.url = "git+https://git.pvv.ntnu.no/Grzegorz/greg-ng.git?ref=main";
@@ -94,6 +97,18 @@
];
});
apps = forAllSystems (system: let
pkgs = nixpkgs.legacyPackages.${system};
in {
gitea-workflows = {
type = "app";
meta.description = "Run all gitea workflows locally";
program = toString (pkgs.writeShellScript "pvv-nixos-config-run-gitea-worflows" ''
${lib.getExe pkgs.gitea-actions-runner} exec -i node:current-trixie
'');
};
});
nixosConfigurations = let
nixosConfig = nixpkgs: name: configurationPath: extraArgs @ {
localSystem ? "x86_64-linux", # buildPlatform
@@ -189,6 +204,12 @@
(final: prev: {
inherit (self.packages.${prev.stdenv.hostPlatform.system}) out-of-your-element;
})
(final: prev: {
# See https://git.pvv.ntnu.no/Drift/issues/issues/369
mjolnir = prev.mjolnir.override {
nodejs = prev.nodejs_22;
};
})
];
};
bekkalokk = stableNixosConfig "bekkalokk" {
@@ -212,10 +233,14 @@
};
skrot = stableNixosConfig "skrot" {
modules = [
self.nixosModules.drumknotty
inputs.disko.nixosModules.disko
inputs.dibbler.nixosModules.default
];
overlays = [inputs.dibbler.overlays.default];
overlays =
[
inputs.dibbler.overlays.default
inputs.worblehat.overlays.default
];
};
shark = stableNixosConfig "shark" {};
wenche = stableNixosConfig "wenche" {};
@@ -287,6 +312,7 @@
rsync-pull-targets = ./modules/rsync-pull-targets.nix;
snakeoil-certs = ./modules/snakeoil-certs.nix;
snappymail = ./modules/snappymail.nix;
drumknotty = ./modules/drumknotty;
};
devShells = forAllSystems (system: {
@@ -343,7 +369,12 @@
//
# Machines
lib.genAttrs allMachines
(machine: self.nixosConfigurations.${machine}.config.system.build.toplevel)
(machine: self.nixosConfigurations.${machine}.config.system.build.toplevel.overrideAttrs (prev: {
passthru =
(prev.passthru or { })
// self.nixosConfigurations.${machine}.config.system.build
// { inherit (self.nixosConfigurations.${machine}) pkgs config; };
}))
//
# Nix-topology
(let
+1 -2
View File
@@ -7,6 +7,7 @@
./services/alps.nix
./services/bluemap.nix
./services/radicale.nix
./services/idp-simplesamlphp
./services/kerberos.nix
./services/mediawiki
@@ -24,8 +25,6 @@
address = with values.hosts.bekkalokk; [ (ipv4 + "/25") (ipv6 + "/64") ];
};
services.btrfs.autoScrub.enable = true;
# Don't change (even during upgrades) unless you know what you are doing.
# See https://search.nixos.org/options?show=system.stateVersion
system.stateVersion = "25.11";
@@ -16,6 +16,7 @@
fileSystems."/" =
{ device = "/dev/sda1";
fsType = "btrfs";
options = [ "relatime" ];
};
fileSystems."/boot" =
+40
View File
@@ -0,0 +1,40 @@
{ config, lib, ... }:
let
domain = "dav.pvv.ntnu.no";
radicalePort = 5232;
in {
services.radicale = {
enable = true;
settings = {
server = {
hosts = [ "127.0.0.1:${toString radicalePort}" ];
};
auth = {
type = "imap";
imap_host = "imap.pvv.ntnu.no";
imap_security = "tls";
};
storage = {
filesystem_folder = "/var/lib/radicale/collections";
};
};
};
services.nginx.virtualHosts."${domain}" = {
forceSSL = true;
enableACME = true;
kTLS = true;
extraConfig = ''
client_max_body_size 128M;
'';
locations."/" = {
proxyPass = "http://127.0.0.1:${toString radicalePort}";
proxyWebsockets = true;
};
};
}
+2 -1
View File
@@ -10,8 +10,9 @@
enableACME = true;
kTLS = true;
locations = {
"= /".return = "302 https://webmail.pvv.ntnu.no/roundcube";
# "= /".return = "302 https://webmail.pvv.ntnu.no/roundcube";
"/roundcube".return = "302 https://webmail.pvv.ntnu.no/";
"/afterlogic_lite".return = "302 https://webmail.pvv.ntnu.no/roundcube";
"/squirrelmail".return = "302 https://webmail.pvv.ntnu.no/roundcube";
"/rainloop".return = "302 https://snappymail.pvv.ntnu.no/";
+2 -37
View File
@@ -29,7 +29,7 @@ in
dicts = with pkgs.aspellDicts; [ en en-computers nb nn fr de it ];
maxAttachmentSize = 20;
hostName = "roundcubeplaceholder.example.com";
hostName = domain;
database = {
host = "postgres.pvv.ntnu.no";
@@ -49,44 +49,9 @@ in
'';
};
services.nginx.virtualHosts."roundcubeplaceholder.example.com" = lib.mkForce { };
# TODO: move this back to `webmail.pvv.ntnu.no/roundcube` subpath
services.nginx.virtualHosts.${domain} = {
kTLS = true;
locations."/roundcube" = {
tryFiles = "$uri $uri/ =404";
index = "index.php";
root = pkgs.linkFarm "roundcube-dir" {
roundcube = "${cfg.package}";
};
extraConfig = ''
location ~ ^/roundcube/(${builtins.concatStringsSep "|" [
# https://wiki.archlinux.org/title/Roundcube
"README"
"INSTALL"
"LICENSE"
"CHANGELOG"
"UPGRADING"
"bin"
"SQL"
".+\\.md"
"\\."
"config"
"temp"
"logs"
]})/? {
deny all;
}
location ~ ^/roundcube/(.+\.php)(/?.*)$ {
fastcgi_split_path_info ^/roundcube(/.+\.php)(/.+)$;
include ${config.services.nginx.package}/conf/fastcgi_params;
include ${config.services.nginx.package}/conf/fastcgi.conf;
fastcgi_index index.php;
fastcgi_pass unix:${config.services.phpfpm.pools.roundcube.socket};
}
'';
};
};
}
+1 -1
View File
@@ -74,7 +74,7 @@ in {
name = "psycopg2";
args = {
host = "/var/run/postgresql";
dbname = "synapse";
database = "synapse";
user = "matrix-synapse";
cp_min = 1;
cp_max = 5;
+1 -1
View File
@@ -43,7 +43,7 @@ in
sshCommand = ''${pkgs.openssh}/bin/ssh -o UserKnownHostsFile='${knownHostsFile}' -i \"$CREDENTIALS_DIRECTORY\"/sshkey'';
in [
"${lib.getExe' pkgs.coreutils "mkdir"} -p '${cfg.minecraftLogsDir}'"
"${lib.getExe pkgs.rsync} ${rsyncArgs} --rsh=\"${sshCommand}\" root@innovation.pvv.ntnu.no:/ '${cfg.minecraftLogsDir}'/"
"${lib.getExe pkgs.rsync} ${rsyncArgs} --rsh=\"${sshCommand}\" root@innovation.pvv.ntnu.no:. '${cfg.minecraftLogsDir}'/"
];
};
};
+15
View File
@@ -23,6 +23,9 @@ in
bind-address = values.services.mysql.ipv4;
skip-networking = 0;
# Useful for the mysqld prometheus exporter
userstat = 1;
# This was needed in order to be able to use all of the old users
# during migration from knakelibrak to bicep in Sep. 2023
secure_auth = 0;
@@ -71,4 +74,16 @@ in
];
};
};
services.logrotate = lib.mkIf (cfg.settings.mysqld.slow-query-log == 1) {
enable = true;
settings.mysql-slowlog = {
files = [ cfg.settings.mysqld.slow-query-log-file ];
frequency = "weekly";
rotate = 12;
create = "0660 mysql mysql";
minsize = "1M";
compress = true;
};
};
}
@@ -127,4 +127,27 @@ in
networking.firewall.allowedTCPPorts = lib.mkIf cfg.enable [ 5432 ];
networking.firewall.allowedUDPPorts = lib.mkIf cfg.enable [ 5432 ];
environment.systemPackages = [
(pkgs.writeShellApplication {
name = "postgres-update-collations.sh";
runtimeInputs = [
config.systemd.package
cfg.package
];
text = ''
run0 --user=postgres psql <${pkgs.writeText "postgres-update-collations.sql" ''
CREATE FUNCTION exec(text) returns text language plpgsql volatile
AS $f$
BEGIN
EXECUTE $1;
RETURN $1;
END;
$f$;
SELECT exec('ALTER DATABASE "' || datname || '" REFRESH COLLATION VERSION') FROM pg_database WHERE datistemplate = false;
''}
'';
})
];
}
+23 -1
View File
@@ -29,7 +29,29 @@
firewall = {
enable = true;
# Allow SSH and HTTP and ports for email and irc
allowedTCPPorts = [ 80 22 194 994 6665 6666 6667 6668 6669 6697 995 993 25 465 587 110 143 993 995 ];
allowedTCPPorts = [
22 # SSH
80 # HTTP
# IRC
194 # IRC
994 # IRC (TLS)
6697 # IRC (SSL)
6665
6666
6667
6668
6669
# EMAIL
25 # STMP
465 # STMP (SSL)
587 # STMP (TLS/STARTTLS)
110 # POP3
995 # POP3 (SSL/TLS)
143 # IMAP
993 # IMAP (SSL/TLS)
];
allowedUDPPorts = [ 80 22 194 994 6665 6666 6667 6668 6669 6697 995 993 25 465 587 110 143 993 995 ];
};
# Use systemd-resolved inside the container
@@ -16,6 +16,7 @@
fileSystems."/" =
{ device = "/dev/disk/by-uuid/4e8667f8-55de-4103-8369-b94665f42204";
fsType = "ext4";
options = [ "relatime" ];
};
fileSystems."/boot" =
+1
View File
@@ -16,6 +16,7 @@
fileSystems."/" =
{ device = "/dev/disk/by-uuid/33825f0d-5a63-40fc-83db-bfa1ebb72ba0";
fsType = "ext4";
options = [ "relatime" ];
};
fileSystems."/boot" =
@@ -28,6 +28,7 @@
fileSystems."/" = {
device = "/dev/mapper/pool-root";
fsType = "ext4";
options = [ "relatime" ];
};
fileSystems."/boot" = {
+2 -2
View File
@@ -93,7 +93,7 @@ in {
no-group = true;
rsh = "${pkgs.openssh}/bin/ssh -o UserKnownHostsFile=%d/ssh-known-hosts -i %d/sshkey";
};
in "${lib.getExe pkgs.rsync} ${rsyncArgs} root@innovation.pvv.ntnu.no:/ ${vanillaSurvival}";
in "${lib.getExe pkgs.rsync} ${rsyncArgs} root@innovation.pvv.ntnu.no:. ${vanillaSurvival}";
ExecStartPost = let
rsyncArgs = lib.cli.toCommandLineShellGNU { } {
archive = true;
@@ -103,7 +103,7 @@ in {
no-group = true;
rsh = "${pkgs.openssh}/bin/ssh -o UserKnownHostsFile=%d/ssh-known-hosts -i %d/sshkey";
};
in "${lib.getExe pkgs.rsync} ${rsyncArgs} --groupmap=root:nginx ${config.services.bluemap.webRoot}/ root@bekkalokk.pvv.ntnu.no:/";
in "${lib.getExe pkgs.rsync} ${rsyncArgs} --groupmap=root:nginx ${config.services.bluemap.webRoot}/ root@bekkalokk.pvv.ntnu.no:.";
LoadCredential = [
"sshkey:${config.sops.secrets."bluemap/ssh-key".path}"
"ssh-known-hosts:${config.sops.secrets."bluemap/ssh-known-hosts".path}"
+24 -4
View File
@@ -1,4 +1,4 @@
{ config, pkgs, ... }:
{ config, pkgs, values, ... }:
let
cfg = config.services.loki;
@@ -9,8 +9,8 @@ in {
configuration = {
auth_enabled = false;
server = {
http_listen_port = 3100;
http_listen_address = "0.0.0.0";
http_listen_port = 31832;
http_listen_address = "127.0.0.1";
grpc_listen_port = 9096;
};
@@ -81,5 +81,25 @@ in {
};
};
networking.firewall.allowedTCPPorts = [ cfg.configuration.server.http_listen_port ];
services.nginx.virtualHosts."loki.pvv.ntnu.no" = {
forceSSL = true;
enableACME = true;
kTLS = true;
locations = {
"/".return = "403";
"/loki/api/v1/push" = {
proxyPass = "http://${cfg.configuration.server.http_listen_address}:${toString cfg.configuration.server.http_listen_port}/loki/api/v1/push";
extraConfig = ''
allow 127.0.0.1;
allow ::1;
allow ${values.ipv4-space};
allow ${values.ipv6-space};
allow ${values.ntnu.ipv4-space};
allow ${values.ntnu.ipv6-space};
deny all;
'';
};
};
};
}
@@ -1,14 +1,12 @@
{ ... }:
{
services.prometheus = {
scrapeConfigs = [
{
job_name = "exim";
scrape_interval = "15s";
static_configs = [{
targets = [ "microbel.pvv.ntnu.no:9636" ];
}];
}
];
};
services.prometheus.scrapeConfigs = [{
job_name = "exim";
scrape_interval = "15s";
scheme = "http";
static_configs = [{
targets = [ "microbel.pvv.ntnu.no:9636" ];
}];
}];
}
@@ -6,32 +6,63 @@
targets = map (port: "${name}.pvv.ntnu.no:${toString port}") ports;
};
nixosMachines = [
"ildkule"
"bekkalokk"
"bicep"
"brzeczyszczykiewicz"
"georg"
"gluttony"
"kommode"
"lupine-1"
"lupine-2"
"lupine-3"
"lupine-4"
"lupine-5"
# TODO: export prometheus stats via apache on temmie
# "temmie"
"wenche"
];
defaultNodeExporterPort = 9100;
defaultSystemdExporterPort = 9101;
defaultNixosExporterPort = 9102;
in {
services.prometheus.scrapeConfigs = [{
job_name = "base_info";
static_configs = [
(mkHostScrapeConfig "ildkule" [ cfg.exporters.node.port cfg.exporters.systemd.port defaultNixosExporterPort ])
(mkHostScrapeConfig "bekkalokk" [ defaultNodeExporterPort defaultSystemdExporterPort defaultNixosExporterPort ])
(mkHostScrapeConfig "bicep" [ defaultNodeExporterPort defaultSystemdExporterPort defaultNixosExporterPort ])
(mkHostScrapeConfig "brzeczyszczykiewicz" [ defaultNodeExporterPort defaultSystemdExporterPort defaultNixosExporterPort ])
(mkHostScrapeConfig "georg" [ defaultNodeExporterPort defaultSystemdExporterPort defaultNixosExporterPort ])
(mkHostScrapeConfig "gluttony" [ defaultNodeExporterPort defaultSystemdExporterPort defaultNixosExporterPort ])
(mkHostScrapeConfig "kommode" [ defaultNodeExporterPort defaultSystemdExporterPort defaultNixosExporterPort ])
(mkHostScrapeConfig "lupine-1" [ defaultNodeExporterPort defaultSystemdExporterPort defaultNixosExporterPort ])
(mkHostScrapeConfig "lupine-2" [ defaultNodeExporterPort defaultSystemdExporterPort defaultNixosExporterPort ])
(mkHostScrapeConfig "lupine-3" [ defaultNodeExporterPort defaultSystemdExporterPort defaultNixosExporterPort ])
(mkHostScrapeConfig "lupine-4" [ defaultNodeExporterPort defaultSystemdExporterPort defaultNixosExporterPort ])
(mkHostScrapeConfig "lupine-5" [ defaultNodeExporterPort defaultSystemdExporterPort defaultNixosExporterPort ])
(mkHostScrapeConfig "temmie" [ defaultNodeExporterPort defaultSystemdExporterPort defaultNixosExporterPort ])
(mkHostScrapeConfig "wenche" [ defaultNodeExporterPort defaultSystemdExporterPort defaultNixosExporterPort ])
(mkHostScrapeConfig "hildring" [ defaultNodeExporterPort ])
(mkHostScrapeConfig "isvegg" [ defaultNodeExporterPort ])
(mkHostScrapeConfig "microbel" [ defaultNodeExporterPort ])
];
}];
services.prometheus.scrapeConfigs = [
{
job_name = "nixos-node";
scheme = "https";
metrics_path = "/prometheus-node-exporter/metrics";
static_configs = map (name: {
labels.hostname = name;
targets = [ "${name}.pvv.ntnu.no:443" ];
}) nixosMachines;
}
{
job_name = "nixos-systemd";
scheme = "https";
metrics_path = "/prometheus-systemd-exporter/metrics";
static_configs = map (name: {
labels.hostname = name;
targets = [ "${name}.pvv.ntnu.no:443" ];
}) nixosMachines;
}
{
job_name = "nixos-flake-input";
scheme = "https";
metrics_path = "/prometheus-nixos-flake-input-exporter/metrics";
static_configs = map (name: {
labels.hostname = name;
targets = [ "${name}.pvv.ntnu.no:443" ];
}) nixosMachines;
}
{
job_name = "non-nixos-node";
scheme = "http";
metrics_path = "/metrics";
static_configs = [
(mkHostScrapeConfig "hildring" [ defaultNodeExporterPort ])
(mkHostScrapeConfig "isvegg" [ defaultNodeExporterPort ])
(mkHostScrapeConfig "microbel" [ defaultNodeExporterPort ])
];
}
];
}
-2
View File
@@ -15,8 +15,6 @@
address = with values.hosts.kommode; [ (ipv4 + "/25") (ipv6 + "/64") ];
};
services.btrfs.autoScrub.enable = true;
services.qemuGuest.enable = true;
# Don't change (even during upgrades) unless you know what you are doing.
+1
View File
@@ -48,6 +48,7 @@
# swap.swapfile.size = "4G";
mountpoint = "/";
mountOptions = [ "relatime" ];
};
};
+1 -1
View File
@@ -136,7 +136,7 @@ in {
picture = {
AVATAR_MAX_FILE_SIZE = 1024 * 1024 * 5;
# NOTE: go any bigger than this, and gitea will freeze your gif >:(
AVATAR_MAX_ORIGIN_SIZE = 1024 * 1024 * 2;
AVATAR_MAX_ORIGIN_SIZE = 1024 * 1024 * 4;
};
actions.ENABLED = true;
webhook.ALLOWED_HOST_LIST = lib.concatStringsSep "," [
+6
View File
@@ -9,6 +9,12 @@
sops.defaultSopsFile = fp /secrets/lupine/lupine.yaml;
boot.binfmt.emulatedSystems = [
"aarch64-linux"
"armv7l-linux"
"i686-linux"
];
systemd.network.networks."30-enp0s31f6" = values.defaultNetworkConfig // {
matchConfig.Name = "enp0s31f6";
address = with values.hosts.${lupineName}; [ (ipv4 + "/25") (ipv6 + "/64") ];
+1
View File
@@ -16,6 +16,7 @@
fileSystems."/" =
{ device = "/dev/disk/by-uuid/224c45db-9fdc-45d4-b3ad-aaf20b3efa8a";
fsType = "ext4";
options = [ "relatime" ];
};
fileSystems."/boot" =
+40 -14
View File
@@ -28,26 +28,52 @@
sops.secrets = {
"dibbler/postgresql/password" = {
owner = "dibbler";
group = "dibbler";
owner = "drumknotty";
group = "drumknotty";
};
"worblehat/postgresql/password" = {
owner = "drumknotty";
group = "drumknotty";
};
};
services.dibbler = {
services.drumknotty = {
enable = true;
kioskMode = true;
limitScreenWidth = 80;
limitScreenHeight = 42;
settings = {
general.quit_allowed = false;
database = {
type = "postgresql";
postgresql = {
username = "pvv_vv";
dbname = "pvv_vv";
host = "postgres.pvv.ntnu.no";
password_file = config.sops.secrets."dibbler/postgresql/password".path;
screen = {
limitWidth = 80;
limitHeight = 42;
};
dibbler = {
enable = true;
settings = {
general.quit_allowed = false;
database = {
type = "postgresql";
postgresql = {
username = "pvv_vv";
dbname = "pvv_vv";
host = "postgres.pvv.ntnu.no";
password_file = config.sops.secrets."dibbler/postgresql/password".path;
};
};
};
};
worblehat = {
enable = true;
settings = {
general.quit_allowed = false;
database = {
type = "postgresql";
postgresql = {
username = "worblehat";
dbname = "worblehat";
host = "postgres.pvv.ntnu.no";
password = config.sops.secrets."worblehat/postgresql/password".path;
};
};
};
};
+1
View File
@@ -31,6 +31,7 @@
type = "filesystem";
format = "ext4";
mountpoint = "/";
mountOptions = [ "relatime" ];
};
};
};
+1
View File
@@ -16,6 +16,7 @@
fileSystems."/" =
{ device = "/dev/disk/by-uuid/c3aed415-0054-4ac5-8d29-75a99cc26451";
fsType = "btrfs";
options = [ "relatime" ];
};
fileSystems."/boot" =
+61 -2
View File
@@ -1,4 +1,4 @@
{ lib, values, ... }:
{ lib, pkgs, values, ... }:
let
# See microbel:/etc/exports
letters = [ "a" "b" "c" "d" "h" "i" "j" "k" "l" "m" "z" ];
@@ -6,6 +6,20 @@ in
{
systemd.targets."pvv-homedirs" = {
description = "PVV Homedir Partitions";
requires = map (l: "pvv-homedir-create-uidmapped-bindmounts@${l}.service") letters;
};
systemd.tmpfiles.settings."10-pvv-homedirs" = {
"/run/pvvhome".d = {
user = "root";
group = "root";
mode = "0755";
};
"/run/pvvhome/by-uid".d = {
user = "root";
group = "root";
mode = "0755";
};
};
systemd.mounts = map (l: {
@@ -17,7 +31,7 @@ in
type = "nfs";
what = "homepvv${l}.pvv.ntnu.no:/export/home/pvv/${l}";
where = "/run/pvv-home-mounts/${l}";
where = "/run/pvvhome/${l}";
options = lib.concatStringsSep "," [
"nfsvers=3"
@@ -54,4 +68,49 @@ in
"rw"
];
}) letters;
systemd.services."pvv-homedir-create-uidmapped-bindmounts@" = {
bindsTo = [ "run-pvvhome-%i.mount" ];
after = [ "run-pvvhome-%i.mount" ];
serviceConfig = {
Type = "oneshot";
};
path = with pkgs; [
coreutils
systemdMinimal
];
scriptArgs = "%i";
script = ''
for dir in "/run/pvvhome/$1"/*/; do
[[ -d "$dir" ]] || continue
uid="$(stat -c '%u' "$dir")"
mountpoint="/run/pvvhome/by-uid/$uid"
mkdir -p "$mountpoint"
unit_name=$(systemd-escape --path --suffix=mount "$mountpoint")
if systemctl --quiet is-active "$unit_name" ||
systemctl --quiet is-failed "$unit_name"; then
echo "Skipping existing mount unit: $unit_name"
continue
fi
systemd-mount \
--collect \
--fsck=no \
--type=none \
--options=bind \
--property=BindsTo=$(systemd-escape --path --suffix=mount "/run/pvvhome/$1") \
--property=After=$(systemd-escape --path --suffix=mount "/run/pvvhome/$1") \
"$dir" \
"$mountpoint" \
|| echo "Failed mounting for uid $uid"
done
'';
};
}
@@ -0,0 +1 @@
target
@@ -0,0 +1,171 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "apache-log-processor"
version = "0.1.0"
dependencies = [
"nix",
"time",
]
[[package]]
name = "bitflags"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "deranged"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
dependencies = [
"powerfmt",
]
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "libc"
version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]]
name = "nix"
version = "0.31.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d"
dependencies = [
"bitflags",
"cfg-if",
"cfg_aliases",
"libc",
]
[[package]]
name = "num-conv"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
[[package]]
name = "num_threads"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
dependencies = [
"libc",
]
[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "syn"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "time"
version = "0.3.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
dependencies = [
"deranged",
"itoa",
"libc",
"num-conv",
"num_threads",
"powerfmt",
"serde_core",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
[[package]]
name = "time-macros"
version = "0.2.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215"
dependencies = [
"num-conv",
"time-core",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
@@ -0,0 +1,19 @@
[package]
name = "apache-log-processor"
version = "0.1.0"
edition = "2024"
autobins = false
license = "MIT"
authors = [
"projects@pvv.ntnu.no",
]
[dependencies]
nix = { version = "0.31.3", features = ["event", "fs", "user"] }
time = { version = "0.3.47", features = ["formatting", "local-offset"] }
[[bin]]
name = "apache-log-processor"
bench = false
path = "src/main.rs"
@@ -0,0 +1,33 @@
{
lib
, rustPlatform
, stdenv
}:
let
cargoToml = fromTOML (builtins.readFile ./Cargo.toml);
cargoLock = ./Cargo.lock;
mainProgram = (lib.head cargoToml.bin).name;
pname = cargoToml.package.name;
in
rustPlatform.buildRustPackage {
inherit pname;
inherit (cargoToml.package) version;
src = lib.fileset.toSource {
root = ./.;
fileset = lib.fileset.unions [
./Cargo.toml
./Cargo.lock
./src
];
};
cargoLock.lockFile = cargoLock;
doCheck = true;
meta = with lib; {
license = licenses.mit;
platforms = platforms.linux;
inherit mainProgram;
};
}
@@ -0,0 +1,321 @@
use nix::{
errno::Errno,
fcntl::{FcntlArg, OFlag, fcntl, open},
sys::{
epoll::{Epoll, EpollCreateFlags, EpollEvent, EpollFlags, EpollTimeout},
stat::Mode,
},
unistd::{User, getegid, geteuid, read, setegid, seteuid, write},
};
use std::{
collections::VecDeque,
os::fd::{AsFd, BorrowedFd, OwnedFd},
path::PathBuf,
process::exit,
};
use time::{OffsetDateTime, format_description};
const READ_BUFFER_SIZE: usize = 8 * 1024;
#[derive(Debug, Clone, Copy)]
enum LogMode {
Access,
Error,
}
fn main() -> Result<(), String> {
let log_mode = match std::env::args().nth(1).as_deref() {
Some("access") => LogMode::Access,
Some("error") => LogMode::Error,
Some(other) => {
return Err(format!(
"invalid log mode `{other}`; expected `access` or `error`"
));
}
None => return Err("missing log mode argument; expected `access` or `error`".to_string()),
};
let tee_file = match log_mode {
LogMode::Access => None,
LogMode::Error => Some(
open(
&PathBuf::from("/var/log/httpd/error.log"),
OFlag::O_WRONLY | OFlag::O_APPEND | OFlag::O_CREAT | OFlag::O_CLOEXEC,
Mode::S_IRUSR | Mode::S_IWUSR,
)
.map_err(|error| format!("failed to open error log for teeing: {error}"))?,
),
};
let stdin = std::io::stdin();
fcntl(stdin.as_fd(), FcntlArg::F_GETFL)
.map(OFlag::from_bits_retain)
.map(|flags| FcntlArg::F_SETFL(flags | OFlag::O_NONBLOCK))
.and_then(|flags| fcntl(stdin.as_fd(), flags))
.map_err(|error| format!("failed to make stdin nonblocking: {error}"))?;
let epoll = Epoll::new(EpollCreateFlags::EPOLL_CLOEXEC)
.map_err(|error| format!("failed to create epoll instance: {error}"))?;
epoll
.add(
stdin.as_fd(),
EpollEvent::new(
EpollFlags::EPOLLIN | EpollFlags::EPOLLERR | EpollFlags::EPOLLHUP,
0,
),
)
.map_err(|error| format!("failed to register stdin with epoll: {error}"))?;
if let Err(error) = event_loop(log_mode, epoll, stdin.as_fd(), tee_file) {
eprintln!("Error: {error}");
exit(1);
}
Ok(())
}
fn event_loop(
log_mode: LogMode,
epoll: Epoll,
stdin_fd: BorrowedFd<'_>,
mut tee_file: Option<OwnedFd>,
) -> Result<(), String> {
let mut events = [EpollEvent::empty(); 1];
let mut pending = VecDeque::new();
loop {
let ready = loop {
match epoll.wait(&mut events, EpollTimeout::NONE) {
Ok(ready) => break ready,
Err(Errno::EINTR) => continue,
Err(error) => {
return Err(format!("epoll wait failed: {error}"));
}
}
};
if ready == 0 {
continue;
}
let mut scratch = [0u8; READ_BUFFER_SIZE];
let eof = loop {
match read(stdin_fd, &mut scratch) {
Ok(0) => break true,
Ok(read_bytes) => pending.extend(scratch[..read_bytes].iter().copied()),
Err(Errno::EINTR) => continue,
Err(Errno::EAGAIN) => break false,
Err(error) => {
return Err(format!("failed to read from stdin: {error}"));
}
}
};
while let Some(newline_index) = pending.iter().position(|byte| *byte == b'\n') {
let line = pending.make_contiguous();
process_line(log_mode, &line[..=newline_index], &mut tee_file)?;
pending.drain(..=newline_index);
}
if eof {
if !pending.is_empty() {
process_line(log_mode, pending.make_contiguous(), &mut tee_file)?;
pending.clear();
}
return Ok(());
}
}
}
fn process_line(
log_mode: LogMode,
line: &[u8],
tee_file: &mut Option<OwnedFd>,
) -> Result<(), String> {
if let Some(tee_file) = tee_file.as_ref() {
write_all_fd(tee_file, line).map_err(|error| {
format!("failed to append to APACHE_LOG_PROCESSOR_TEE_FILE: {error}")
})?;
}
if let Some(user) =
parse_username_from_line(line).and_then(|name| User::from_name(name).ok().flatten())
{
let identity = EffectiveIdentity::switch_to(&user).map_err(|error| {
format!(
"failed to switch effective identity to {} (uid {}, gid {}): {error}",
user.name, user.uid, user.gid
)
})?;
let result: Result<(), String> = (|| {
let dir = user.dir.join("nobackup/weblogs");
if !dir.is_dir() {
return Err(format!(
"logs directory {} does not exist for user {}",
dir.display(),
user.name
));
}
let now = OffsetDateTime::now_local()
.unwrap_or_else(|_| OffsetDateTime::now_utc())
.format(&format_description::parse("[year]-[month]-[day]").unwrap())
.map_err(|error| {
format!("failed to format current date for log file name: {error}")
})?;
let logfile = dir.join(match log_mode {
LogMode::Access => format!("access-{now}.log"),
LogMode::Error => format!("error-{now}.log"),
});
let fd = open(
&logfile,
OFlag::O_WRONLY | OFlag::O_APPEND | OFlag::O_CREAT | OFlag::O_CLOEXEC,
Mode::S_IRUSR
| Mode::S_IWUSR
| Mode::S_IRGRP
| Mode::S_IROTH
| Mode::S_IWGRP
| Mode::S_IWOTH,
)
.map_err(|error| format!("failed to open log file for user {}: {error}", user.name))?;
write_all_fd(fd.as_fd(), line).map_err(|error| {
format!(
"failed to append to log file for user {}: {error}",
user.name
)
})?;
Ok(())
})();
if let Err(error) = result {
eprintln!("Error processing log line for user {}: {error}", user.name);
}
identity.restore().map_err(|error| {
format!(
"failed to restore original effective identity after handling {}: {error}",
user.name
)
})?;
}
Ok(())
}
fn parse_username_from_line(line: &[u8]) -> Option<&str> {
line.splitn(8, |&b| b == b' ')
.nth(6)
.and_then(|path| {
path.strip_prefix(b"/~")
.and_then(|rest| rest.split(|&b| b == b'/').next())
})
.or_else(|| {
line.windows(b"/home/pvv/".len())
.enumerate()
.find_map(|(start, window)| {
(window == b"/home/pvv/")
.then_some(start + b"/home/pvv/".len())
.and_then(|start| line.get(start..))
.filter(|rest| rest.get(1) == Some(&b'/'))
.and_then(|rest| rest.get(2..))
.and_then(|rest| rest.split(|&b| b == b'/').next())
})
})
.filter(|segment| !segment.is_empty())
.and_then(|segment| std::str::from_utf8(segment).ok())
}
fn write_all_fd<Fd: AsFd>(fd: Fd, mut buffer: &[u8]) -> nix::Result<()> {
while !buffer.is_empty() {
match write(fd.as_fd(), buffer) {
Ok(0) => return Err(Errno::EIO),
Ok(written) => buffer = &buffer[written..],
Err(Errno::EINTR) => continue,
Err(error) => return Err(error),
}
}
Ok(())
}
struct EffectiveIdentity {
saved_euid: nix::unistd::Uid,
saved_egid: nix::unistd::Gid,
restored: bool,
}
impl EffectiveIdentity {
fn switch_to(user: &User) -> nix::Result<Self> {
let guard = Self {
saved_euid: geteuid(),
saved_egid: getegid(),
restored: false,
};
setegid(user.gid)?;
if let Err(error) = seteuid(user.uid) {
let _ = setegid(guard.saved_egid);
return Err(error);
}
Ok(guard)
}
fn restore(mut self) -> nix::Result<()> {
let restore_uid = seteuid(self.saved_euid);
let restore_gid = setegid(self.saved_egid);
self.restored = true;
restore_uid?;
restore_gid?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_user_from_access_log() {
let inputs = [(
"1.2.3.4 - - [25/May/2026:10:07:24 +0200] \"GET /~oysteikt/ HTTP/2.0\" 200 3708",
"oysteikt",
)];
for (line, expected_user) in inputs {
let parsed_user = parse_username_from_line(line.as_bytes());
assert_eq!(
parsed_user,
Some(expected_user),
"Failed to parse user from line: {line}"
);
}
}
#[test]
fn test_parse_user_from_error_log() {
let inputs = [(
"[Sat May 09 20:45:21.480016 2026] [authz_core:error] [pid 3555:tid 3617] [remote 1::2:42000] AH01630: client denied by server configuration: /home/pvv/d/oysteikt/web-docs/.git",
"oysteikt",
)];
for (line, expected_user) in inputs {
let parsed_user = parse_username_from_line(line.as_bytes());
assert_eq!(
parsed_user,
Some(expected_user),
"Failed to parse user from line: {line}"
);
}
}
}
+5 -432
View File
@@ -1,437 +1,10 @@
{ config, lib, pkgs, ... }:
let
cfg = config.services.httpd;
homeLetters = [ "a" "b" "c" "d" "h" "i" "j" "k" "l" "m" "z" ];
phpOptions = lib.concatStringsSep "\n" (lib.mapAttrsToList (k: v: "${k} = ${v}"){
display_errors = "Off";
display_startup_errors = "Off";
post_max_size = "40M";
upload_max_filesize = "40M";
});
# https://nixos.org/manual/nixpkgs/stable/#ssec-php-user-guide-installing-with-extensions
phpEnv = pkgs.php.buildEnv {
extensions = { all, ... }: with all; [
bz2
curl
decimal
gd
imagick
mysqli
mysqlnd
pgsql
posix
protobuf sqlite3
uuid
xml
xsl
zlib
zstd
pdo
pdo_mysql
pdo_pgsql
pdo_sqlite
];
extraConfig = phpOptions;
};
perlEnv = (pkgs.perl.withPackages (ps: with ps; [
pkgs.exiftool
pkgs.ikiwiki
pkgs.irssi
pkgs.nix.libs.nix-perl-bindings
CGI
DBDPg
DBDSQLite
DBDmysql
DBI
Git
ImageMagick
JSON
TemplateToolkit
])).overrideAttrs (prev: {
# NOTE: `pkgs.perl.propagatedBuildInputs` don't actually propagate through the
# wrapper derivation created by `withPackages`. This should compensate
# for that.
postBuild = prev.postBuild + ''
cp -r '${pkgs.perl}/nix-support' "$out"/nix-support
'';
});
# https://nixos.org/manual/nixpkgs/stable/#python.buildenv-function
pythonEnv = pkgs.python3.buildEnv.override {
extraLibs = with pkgs.python3Packages; [
legacy-cgi
matplotlib
requests
];
ignoreCollisions = true;
};
# https://nixos.org/manual/nixpkgs/stable/#sec-building-environment
fhsEnv = pkgs.buildEnv {
name = "userweb-env";
ignoreCollisions = true;
paths = with pkgs; [
bash
config.services.bro.instances.userweb-sendmail.client.package
perlEnv
pythonEnv
phpEnv
]
++ (with phpEnv.packages; [
# composer
])
++ [
# Useful packages for homepages
exiftool
gnuplot
ikiwiki-full
imagemagick
jhead
ruby
sbcl
sourceHighlight
# Missing packages from tom
# blosxom
# pyblosxom
# mediawiki (TODO: do people host their own mediawikis in userweb?)
# nanoblogger
# Version control
cvs
rcs
git
# Compression/Archival
bzip2
gnutar
gzip
lz4
unzip
xz
zip
zstd
# Other tools you might expect to find on a normal system
acl
coreutils-full
curl
diffutils
file
findutils
gawk
glibc.getent
strace
systemd
gnugrep
gnumake
gnupg
gnused
less
man
util-linux
vim
wget
which
xdg-utils
];
extraOutputsToInstall = [
"man"
"doc"
];
};
in
{
imports = [
./httpd.nix
./log-processor.nix
./mail.nix
./module.nix
./packages.nix
./passwd-sync.nix
];
sops.secrets = {
"httpd/passwd-ssh-key" = { };
"httpd/ssh-known-hosts" = { };
};
services.httpd = {
enable = true;
adminAddr = "drift@pvv.ntnu.no";
# TODO: consider upstreaming systemd support
# TODO: mod_log_journald in v2.5
package = pkgs.apacheHttpd.overrideAttrs (prev: {
nativeBuildInputs = prev.nativeBuildInputs ++ [ pkgs.pkg-config ];
buildInputs = prev.buildInputs ++ [ pkgs.systemdLibs ];
configureFlags = prev.configureFlags ++ [ "--enable-systemd" ];
});
enablePHP = true;
phpPackage = phpEnv;
inherit phpOptions;
enablePerl = true;
# TODO: mod_log_journald in v2.5
extraModules = [
"systemd"
"userdir"
{
name = "perl";
path = let
mod_perl = pkgs.symlinkJoin {
name = "userweb_modperl_with_custom_perl_env";
ignoreCollisions = true;
paths = [
(pkgs.apacheHttpdPackages.mod_perl.override {
apacheHttpd = cfg.package.out;
})
perlEnv
];
};
in "${mod_perl}/modules/mod_perl.so";
}
];
extraConfig = ''
TraceEnable on
LogLevel warn rewrite:trace3
ScriptLog ${cfg.logDir}/cgi.log
'';
virtualHosts."temmie.pvv.ntnu.no" = {
forceSSL = true;
enableACME = true;
serverAliases = [
"www2.pvv.ntnu.no"
];
extraConfig = ''
UserDir ${lib.concatMapStringsSep " " (l: "/home/pvv/${l}/*/web-docs") homeLetters}
UserDir disabled root
AddHandler cgi-script .cgi
DirectoryIndex index.html index.html.var index.php index.php3 index.cgi index.phtml index.shtml meg.html
SetEnvIf Request_URI "^/~([^/]+)" USERDIR_USER=$1
<Directory "/home/pvv/?/*/web-docs">
Options MultiViews Indexes SymLinksIfOwnerMatch ExecCGI IncludesNoExec
AllowOverride All
Require all granted
</Directory>
<DirectoryMatch "^/home/pvv/.*/web-docs/(${lib.concatStringsSep "|" [
"\\.git"
"\\.hg"
"\\.svn"
"\\.ssh"
"\\.env"
"\\.envrc"
"\\.bzr"
"\\.venv"
"CVS"
"RCS"
".*\\.swp"
".*\\.bak"
".*~"
]})(/|$)">
AllowOverride All
Require all denied
</DirectoryMatch>
'';
};
};
networking.firewall.allowedTCPPorts = [
80
443
];
# socket activation comes in v2.5
# systemd.sockets.httpd = {
# wantedBy = [ "sockets.target" ];
# description = "HTTPD socket";
# listenStreams = [
# "0.0.0.0:80"
# "0.0.0.0:443"
# ];
# };
# NOTE: 54 -> 33, this is the UID/GID we used for www-data on tom in the past.
# Any files accessed by or created by httpd will do so over NFS with this
# UID/GID pair as its credentials.
# This overlaps with the hardcoded `disnix` uid in nixpkgs, but we *probably*
# won't be using that for the foreseeable future.
users.users."wwwrun".uid = lib.mkForce 33;
users.groups."wwwrun".gid = lib.mkForce 33;
systemd.services.httpd = {
after = [ "pvv-homedirs.target" ];
requires = [ "pvv-homedirs.target" ];
environment = {
PATH = lib.mkForce "/usr/bin";
};
serviceConfig = {
Type = lib.mkForce "notify";
ExecStartPre = let
rsyncCommand = ''${lib.getExe pkgs.rsync} -e "${pkgs.openssh}/bin/ssh -o UserKnownHostsFile=%d/ssh-known-hosts -i %d/sshkey" -avz'';
in lib.mkForce [
"${lib.getExe (pkgs.writeShellApplication {
name = "http-exec-start-pre-remove-old-semaphores";
text = ''
# Get rid of old semaphores. These tend to accumulate across
# server restarts, eventually preventing it from restarting
# successfully.
for i in $(${pkgs.util-linux}/bin/ipcs -s | grep ' ${cfg.user} ' | cut -f2 -d ' '); do
${pkgs.util-linux}/bin/ipcrm -s "$i"
done
'';
})}"
# "${pkgs.systemd}/bin/resolvectl query smtp.pvv.ntnu.no"
"${pkgs.strace}/bin/strace ${pkgs.glibc.getent}/bin/getent ahosts smtp.pvv.ntnu.no"
"${rsyncCommand} pvv@smtp.pvv.ntnu.no:/etc/passwd /run/httpd/pamunix-sync/"
"${rsyncCommand} pvv@smtp.pvv.ntnu.no:/etc/group /run/httpd/pamunix-sync/"
# "+|echo 'wwwrun:x:54:54:Apache httpd user:/var/empty:/run/current-system/sw/bin/nologin' >> /run/httpd/pamunix-sync/passwd"
# "+|echo 'root:x:0:0:System administrator:/root:/run/current-system/sw/bin/bash' >> /run/httpd/pamunix-sync/passwd"
# "+|echo 'wwwrun:x:54:' >> /run/httpd/pamunix-sync/group"
# "${rsyncCommand} pvv@smtp.pvv.ntnu.no:/etc/shadow /run/httpd/pamunix-sync/"
(let
args = lib.cli.toCommandLineShellGNU { } {
passwd-file = "/run/httpd/pamunix-sync/passwd";
group-file = "/run/httpd/pamunix-sync/group";
output-dir = "/run/httpd/systemd-userdb";
shadow-file = pkgs.emptyFile;
email-domain = "pvv.ntnu.no";
ignore-user-file = toString ./ignore_user_file.txt;
ignore-group-file = toString ./ignore_group_file.txt;
set-default-umask = "0077";
set-default-mount-no-devices = "true";
set-default-mount-no-suid = "true";
set-default-mount-no-execute = "false";
};
in ''${lib.getExe pkgs.passwd2systemd-users} ${args}'')
"${lib.getExe' pkgs.coreutils "shred"} -u /run/httpd/pamunix-sync/passwd /run/httpd/pamunix-sync/group"
];
ExecStart = lib.mkForce "${cfg.package}/bin/httpd -D FOREGROUND -f /etc/httpd/httpd.conf -k start";
ExecReload = lib.mkForce "${cfg.package}/bin/httpd -f /etc/httpd/httpd.conf -k graceful";
ExecStop = lib.mkForce "";
KillMode = "mixed";
LoadCredential=[
"sshkey:${config.sops.secrets."httpd/passwd-ssh-key".path}"
"ssh-known-hosts:${config.sops.secrets."httpd/ssh-known-hosts".path}"
];
ConfigurationDirectory = [ "httpd" ];
LogsDirectory = [ "httpd" ];
LogsDirectoryMode = "0700";
AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" "CAP_SYS_PTRACE" ];
CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" "CAP_SYS_PTRACE" ];
# LockPersonality = true;
PrivateDevices = true;
PrivateTmp = true;
# NOTE: this removes CAP_NET_BIND_SERVICE...
# PrivateUsers = true;
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = "tmpfs";
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectSystem = true;
RemoveIPC = true;
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_UNIX"
"AF_NETLINK"
];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SocketBindDeny = "any";
SocketBindAllow = [
"tcp:80"
"tcp:443"
];
SystemCallArchitectures = "native";
# SystemCallFilter = [
# "@system-service"
# ];
UMask = "0077";
RuntimeDirectory = [
"httpd/root-mnt"
"httpd/pamunix-sync"
"httpd/systemd-userdb"
];
RootDirectory = "/run/httpd/root-mnt";
MountAPIVFS = true;
BindReadOnlyPaths = [
builtins.storeDir
"/etc"
"/dev/null"
# NCSD socket
# "/var/run"
# "/var/run/systemd/resolve"
"/etc/resolv.conf"
"/var/lib/acme"
"/run/httpd/systemd-userdb:/etc/userdb"
"${pkgs.writeText "userweb-fake-nsswitch.conf" ''
passwd: systemd files
group: systemd files
shadow: systemd files
sudoers: files
hosts: mymachines resolve [!UNAVAIL=return] files myhostname dns
networks: files
ethers: files
services: files
protocols: files
rpc: files
subuid: files
subgid: files
''}:/etc/nsswitch.conf"
"${fhsEnv}/bin:/bin"
"${fhsEnv}/sbin:/sbin"
"${fhsEnv}/lib:/lib"
"${fhsEnv}/share:/share"
] ++ (lib.mapCartesianProduct ({ parent, child }: "${fhsEnv}${child}:${parent}${child}") {
parent = [
"/local"
"/opt"
"/opt/local"
"/store"
"/store/gnu"
"/usr"
"/usr/local"
];
child = [
"/bin"
"/sbin"
"/lib"
"/libexec"
"/include"
"/share"
];
});
BindPaths = map (l: "/run/pvv-home-mounts/${l}:/home/pvv/${l}") homeLetters;
};
};
# TODO: create phpfpm pools with php environments that contain packages similar to those present on tom
}
+318
View File
@@ -0,0 +1,318 @@
{ config, lib, pkgs, ... }:
let
cfg = config.services.httpd;
mcfg = config.services.pvv-userweb;
in
{
services.httpd = {
enable = true;
adminAddr = "drift@pvv.ntnu.no";
# TODO: consider upstreaming systemd support
# TODO: mod_log_journald in v2.5
package = pkgs.apacheHttpd.overrideAttrs (prev: {
nativeBuildInputs = prev.nativeBuildInputs ++ [ pkgs.pkg-config ];
buildInputs = prev.buildInputs ++ [ pkgs.systemdLibs ];
configureFlags = prev.configureFlags ++ [ "--enable-systemd" ];
});
enablePHP = true;
phpPackage = mcfg.php.env;
phpOptions = mcfg.php.options;
# NOTE: we include our own `mod_perl` in `extraModules` instead.
enablePerl = false;
# NOTE: we include `mod_userdir` in `extraModules` and configure this in `extraConfig` ourselves.
# enableUserDir = false;
# TODO: mod_log_journald in v2.5
extraModules = [
"systemd"
"userdir"
{
name = "perl";
path = let
mod_perl = pkgs.symlinkJoin {
name = "userweb_modperl_with_custom_perl_env";
ignoreCollisions = true;
paths = [
(pkgs.apacheHttpdPackages.mod_perl.override {
apacheHttpd = cfg.package.out;
})
mcfg.perl.env
];
};
in "${mod_perl}/modules/mod_perl.so";
}
];
logPerVirtualHost = false;
extraConfig = lib.mkIf mcfg.debugMode ''
TraceEnable on
LogLevel warn rewrite:trace3
'';
virtualHosts."temmie.pvv.ntnu.no" = {
forceSSL = true;
enableACME = true;
serverAliases = [
"www2.pvv.ntnu.no"
];
extraConfig = ''
<Directory "${pkgs.emptyDirectory}">
Require all denied
LogLevel authz_core:crit
</Directory>
CustomLog "${cfg.logDir}/access.log" combined
CustomLog "/run/httpd-log-processor-access.fifo" combined
ErrorLog "/run/httpd-log-processor-error.fifo"
ScriptLog "${cfg.logDir}/cgi.log"
UserDir ${lib.concatMapStringsSep " " (l: "/home/pvv/${l}/*/web-docs") mcfg.homeLetters}
UserDir disabled root
UserDir disabled pvv
AddHandler cgi-script .cgi
DirectoryIndex ${lib.concatStringsSep " " [
"index.htm"
"index.html"
"index.html.var"
"index.shtml"
"index.xhtml"
"index.php"
"index.php3"
"index.php4"
"index.php5"
"index.php7"
"index.php8"
"index.pht"
"index.phtml"
"index.cgi"
"index.txt"
"meg.html"
]}
SetEnvIf Request_URI "^/~([^/]+)" USERDIR_USER=$1
<Directory "/home/pvv/?/*/web-docs">
Options MultiViews Indexes SymLinksIfOwnerMatch ExecCGI IncludesNoExec
AllowOverride All
Require all granted
</Directory>
${lib.concatMapStringsSep "\n" (d: ''
<DirectoryMatch "/${d}(/|$)">
Require all denied
</DirectoryMatch>
'') [
"\\.git"
"\\.hg"
"\\.svn"
"\\.ssh"
"\\.bzr"
"\\.venv"
"CVS"
"RCS"
".*\\.bak"
".*\\.bak.*"
".*\\.bkp"
".*\\.bkp.*"
".*\\.backup"
".*\\.backup.*"
]}
${lib.concatMapStringsSep "\n" (d: ''
<Files "${d}">
Require all denied
</Files>
'') [
".env"
".env.*"
".envs"
".envs.*"
".envrc"
"*.swp"
"*~"
"*.bak"
"*.bak*"
"*.bkp"
"*.bkp*"
"*.backup"
"*.backup*"
"*.lck"
"*.lock"
"LCK..*"
]}
<FilesMatch ".+\.ph(p[34578]?|t|tml)$">
SetHandler application/x-httpd-php
</FilesMatch>
<FilesMatch ".+\.phps$">
SetHandler application/x-httpd-php-source
Require all denied
</FilesMatch>
<FilesMatch "\.pl$">
SetHandler modperl
PerlResponseHandler ModPerl::Registry
Options +ExecCGI
</FilesMatch>
'';
};
};
networking.firewall.allowedTCPPorts = [
80
443
];
# socket activation comes in v2.5
# systemd.sockets.httpd = {
# wantedBy = [ "sockets.target" ];
# description = "HTTPD socket";
# listenStreams = [
# "0.0.0.0:80"
# "0.0.0.0:443"
# ];
# };
# NOTE: 54 -> 33, this is the UID/GID we used for www-data on tom in the past.
# Any files accessed by or created by httpd will do so over NFS with this
# UID/GID pair as its credentials.
# This overlaps with the hardcoded `disnix` uid in nixpkgs, but we *probably*
# won't be using that for the foreseeable future.
users.users."wwwrun".uid = lib.mkForce 33;
users.groups."wwwrun".gid = lib.mkForce 33;
systemd.targets.userweb = {
description = "PVV HTTPD UserWeb";
};
systemd.slices.system-userweb = {
description = "PVV HTTPD UserWeb";
};
systemd.services.httpd = {
after = [
"pvv-homedirs.target"
"httpd-log-processor@access.socket"
"httpd-log-processor@error.socket"
];
requires = [
"pvv-homedirs.target"
"httpd-log-processor@access.socket"
"httpd-log-processor@error.socket"
];
requiredBy = [ "userweb.target" ];
environment = {
PATH = lib.mkForce "/usr/bin";
};
serviceConfig = {
Type = lib.mkForce "notify";
ExecStart = lib.mkForce "${cfg.package}/bin/httpd -D FOREGROUND -f /etc/httpd/httpd.conf -k start";
ExecReload = lib.mkForce "${cfg.package}/bin/httpd -f /etc/httpd/httpd.conf -k graceful";
ExecStop = lib.mkForce "";
KillMode = "mixed";
Slice = "system-userweb.slice";
ConfigurationDirectory = [ "httpd" ];
LogsDirectory = [ "httpd" ];
LogsDirectoryMode = "0700";
AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ] ++ lib.optionals mcfg.debugMode [ "CAP_SYS_PTRACE" ];
CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ] ++ lib.optionals mcfg.debugMode [ "CAP_SYS_PTRACE" ];
LockPersonality = !mcfg.debugMode;
PrivateDevices = true;
PrivateTmp = true;
# NOTE: this removes CAP_NET_BIND_SERVICE...
# PrivateUsers = true;
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = "tmpfs";
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectSystem = true;
RemoveIPC = true;
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_UNIX"
"AF_NETLINK"
];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SocketBindDeny = "any";
SocketBindAllow = [
"tcp:80"
"tcp:443"
];
SystemCallArchitectures = "native";
SystemCallFilter = lib.mkIf (!mcfg.debugMode) [ "@system-service" ];
UMask = "0077";
RuntimeDirectoryMode = "0750";
RuntimeDirectory = [ "httpd/root-mnt" ];
RootDirectory = "/run/httpd/root-mnt";
MountAPIVFS = true;
BindReadOnlyPaths = [
builtins.storeDir
"/etc"
"/dev/null"
"/var/lib/acme"
"/var/run/nscd"
"${mcfg.fhsEnv}/bin:/bin"
"${mcfg.fhsEnv}/sbin:/sbin"
"${mcfg.fhsEnv}/lib:/lib"
"${mcfg.fhsEnv}/share:/share"
] ++ (lib.mapCartesianProduct ({ parent, child }: "${mcfg.fhsEnv}${child}:${parent}${child}") {
parent = [
"/local"
"/opt"
"/opt/local"
"/store"
"/store/gnu"
"/usr"
"/usr/local"
"/run/current-system/sw"
];
child = [
"/bin"
"/sbin"
"/lib"
"/libexec"
"/include"
"/share"
];
});
BindPaths = (lib.mapCartesianProduct ({ directoryFn, letter }: "/run/pvvhome/${letter}:${directoryFn letter}${letter}") {
directoryFn = [
(_: "/home/pvv/")
(l: "/amd/homepvv${l}/")
];
letter = mcfg.homeLetters;
}) ++ [
"/run/httpd-log-processor-access.fifo"
"/run/httpd-log-processor-error.fifo"
];
};
};
# TODO: create phpfpm pools with php environments that contain packages similar to those present on tom
}
@@ -0,0 +1,113 @@
{ config, lib, pkgs, values, ... }:
let
mcfg = config.services.pvv-userweb;
in
{
systemd.targets.sockets.wants = [
"httpd-log-processor@access.socket"
"httpd-log-processor@error.socket"
];
systemd.sockets."httpd-log-processor@" = lib.mkIf config.services.httpd.enable {
requiredBy = [ "userweb.target" ];
socketConfig = {
ListenFIFO = "/run/httpd-log-processor-%i.fifo";
RemoveOnStop = true;
SocketUser = "wwwrun";
SocketGroup = "wwwrun";
SocketMode = "0600";
};
};
systemd.services."httpd-log-processor@" = lib.mkIf config.services.httpd.enable {
requiredBy = [ "userweb.target" ];
after = [ "httpd-passwd-sync.service" ];
requires = [ "httpd-passwd-sync.service" ];
serviceConfig = {
User = "wwwrun";
Group = "wwwrun";
Slice = "system-userweb.slice";
Restart = "on-failure";
StandardInput = "socket";
StandardOutput = "journal";
StandardError = "journal";
ExecStart = "${lib.getExe mcfg.apacheLogProcessorPackage} %i";
AmbientCapabilities = [ "CAP_SETUID" "CAP_SETGID" ];
CapabilityBoundingSet = [ "CAP_SETUID" "CAP_SETGID" ];
DeviceAllow = [ "" ];
IPAddressDeny = "any";
LockPersonality = true;
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
PrivateDevices = true;
PrivateIPC = true;
PrivateNetwork = true;
PrivateTmp = true;
PrivateUsers = false;
ProcSubset = "pid";
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = "tmpfs";
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProtectSystem = "strict";
RemoveIPC = true;
RestrictAddressFamilies = [ "none" ];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SocketBindDeny = "any";
SystemCallArchitectures = "native";
SystemCallFilter = [
"@system-service"
"@setuid"
"~@resources"
];
UMask = "0077";
RootDirectory = "/run/httpd-log-processor-%i/root-mnt";
MountAPIVFS = true;
RuntimeDirectoryMode = "0750";
RuntimeDirectory = [ "httpd-log-processor-%i/root-mnt" ];
BindReadOnlyPaths = [
builtins.storeDir
"/etc"
"/var/lib/httpd-passwd-sync/passwd:/etc/passwd"
"/var/lib/httpd-passwd-sync/group:/etc/group"
"${pkgs.writeText "userweb-fake-nsswitch.conf" ''
passwd: files
group: files
shadow: files
sudoers: files
hosts: mymachines resolve [!UNAVAIL=return] files myhostname dns
networks: files
ethers: files
services: files
protocols: files
rpc: files
subuid: files
subgid: files
''}:/etc/nsswitch.conf"
] ++ lib.optionals mcfg.debugMode [
"/bin"
];
BindPaths = map (l: "/run/pvvhome/${l}:/home/pvv/${l}") mcfg.homeLetters ++ [
"/var/log/httpd"
];
};
};
}
+1
View File
@@ -70,6 +70,7 @@
serviceConfig = {
User = "nullmailer-user";
Group = "nullmailer-user";
Slice = "system-userweb.slice";
ReadWritePaths = [
"/var/spool/nullmailer"
+118
View File
@@ -0,0 +1,118 @@
{ config, lib, pkgs, ... }:
let
cfg = config.services.pvv-userweb;
in
{
options.services.pvv-userweb = {
enable = lib.mkEnableOption "" // {
default = true;
};
debugMode = lib.mkEnableOption "";
apacheLogProcessorPackage = lib.mkOption {
type = lib.types.package;
default = pkgs.callPackage ./apache-log-processor { };
};
homeLetters = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ "a" "b" "c" "d" "h" "i" "j" "k" "l" "m" "z" ];
readOnly = true;
};
packages = lib.mkOption {
type = lib.types.listOf lib.types.package;
default = _: [ ];
};
php.extensions = lib.mkOption {
type = lib.types.functionTo (lib.types.listOf lib.types.package);
default = _: [ ];
};
php.options = lib.mkOption {
type = lib.types.attrsOf lib.types.str;
default = {
display_errors = "Off";
display_startup_errors = "Off";
post_max_size = "40M";
upload_max_filesize = "40M";
};
apply = attrs: lib.concatStringsSep "\n" (lib.mapAttrsToList (k: v: "${k} = ${v}") attrs);
};
# https://nixos.org/manual/nixpkgs/stable/#ssec-php-user-guide-installing-with-extensions
php.env = lib.mkOption {
type = lib.types.package;
readOnly = true;
default = pkgs.php.buildEnv {
extensions = cfg.php.extensions;
extraConfig = cfg.php.options;
};
};
perl.packages = lib.mkOption {
type = lib.types.functionTo (lib.types.listOf lib.types.package);
default = _: [ ];
};
perl.env = lib.mkOption {
type = lib.types.package;
readOnly = true;
default = (pkgs.perl.withPackages cfg.perl.packages).overrideAttrs (prev: {
# NOTE: `pkgs.perl.propagatedBuildInputs` don't actually propagate through the
# wrapper derivation created by `withPackages`. This should compensate
# for that.
postBuild = prev.postBuild + ''
cp -r '${pkgs.perl}/nix-support' "$out"/nix-support
'';
});
};
python3.packages = lib.mkOption {
type = lib.types.functionTo (lib.types.listOf lib.types.package);
default = _: [ ];
};
python3.env = lib.mkOption {
type = lib.types.package;
readOnly = true;
default = pkgs.python3.buildEnv.override {
extraLibs = cfg.python3.packages pkgs.python3Packages;
ignoreCollisions = true;
};
};
fhsEnv = lib.mkOption {
type = lib.types.package;
readOnly = true;
default = let
in pkgs.buildEnv {
name = "userweb-env";
ignoreCollisions = true;
paths = with pkgs; [
bash
config.services.bro.instances.userweb-sendmail.client.package
cfg.perl.env
cfg.python3.env
cfg.php.env
] ++ cfg.packages;
extraOutputsToInstall = [
"man"
"doc"
];
};
};
};
config = lib.mkIf cfg.enable {
services.pvv-userweb.packages = lib.mkIf cfg.debugMode (with pkgs; [
glibc.getent
strace
systemd
]);
};
}
+104
View File
@@ -0,0 +1,104 @@
{ pkgs, ... }:
{
services.pvv-userweb = {
packages = with pkgs; [
# Useful packages for homepages
exiftool
gnuplot
ikiwiki-full
imagemagick
jhead
ruby
sbcl
sourceHighlight
# Missing packages from tom
# blosxom
# pyblosxom
# mediawiki (TODO: do people host their own mediawikis in userweb?)
# nanoblogger
# Version control
cvs
rcs
git
# Compression/Archival
bzip2
gnutar
gzip
lz4
unzip
xz
zip
zstd
# Other tools you might expect to find on a normal system
acl
coreutils-full
curl
diffutils
file
findutils
gawk
gnugrep
gnumake
gnupg
gnused
less
man
util-linux
vim
wget
which
xdg-utils
];
php.extensions = { all, ... }: with all; [
bz2
curl
decimal
gd
imagick
mysqli
mysqlnd
pgsql
posix
protobuf sqlite3
uuid
xml
xsl
zlib
zstd
pdo
pdo_mysql
pdo_pgsql
pdo_sqlite
];
perl.packages = perlPkgs: with perlPkgs; [
pkgs.exiftool
pkgs.ikiwiki
pkgs.irssi
pkgs.nix.libs.nix-perl-bindings
CGI
DBDPg
DBDSQLite
DBDmysql
DBI
Git
ImageMagick
JSON
TemplateToolkit
];
python3.packages = pythonPkgs: with pythonPkgs; [
legacy-cgi
matplotlib
requests
];
};
}
@@ -0,0 +1,147 @@
{ config, lib, pkgs, values, ... }:
let
mcfg = config.services.pvv-userweb;
in
{
config = lib.mkIf mcfg.enable {
sops.secrets = {
"httpd/passwd-ssh-key" = { };
"httpd/ssh-known-hosts" = { };
};
# NOTE: because we are running as `DynamicUser` and we want the result files to be available to
# other services, this directory needs to be created via systemd-tmpfiles
systemd.tmpfiles.settings."10-httpd-passwd-sync"."/var/lib/httpd-passwd-sync".d = {
user = "root";
group = "root";
mode = "0700";
};
systemd.timers.httpd-passwd-sync = {
wantedBy = [ "timers.target" ];
timerConfig = {
OnCalendar = "daily";
Persistent = true;
Unit = "httpd-passwd-sync.service";
};
};
systemd.services."httpd-passwd-sync" = {
requiredBy = [ "userweb.target" ];
after = [
"systemd-tmpfiles-setup.service"
"systemd-tmpfiles-resetup.service"
];
serviceConfig = {
Type = "oneshot";
Slice = "system-userweb.slice";
Restart = "on-failure";
RestartSec = "3s";
DynamicUser = true;
LoadCredential = [
"sshkey:${config.sops.secrets."httpd/passwd-ssh-key".path}"
"ssh-known-hosts:${config.sops.secrets."httpd/ssh-known-hosts".path}"
];
ExecStart = let
rsyncArgs = lib.cli.toCommandLineShellGNU { } {
verbose = true;
compress = true;
rsh = "${lib.getExe' pkgs.openssh "ssh"} -o BatchMode=yes -o UserKnownHostsFile=%d/ssh-known-hosts -i %d/sshkey";
};
inputDir = "/run/httpd-passwd-sync/in";
wipDir = "/run/httpd-passwd-sync/wip";
outputDir = "/var/lib/httpd-passwd-sync";
in [
"${lib.getExe pkgs.rsync} ${rsyncArgs} pvv@smtp.pvv.ntnu.no:/etc/passwd ${inputDir}/"
"${lib.getExe pkgs.rsync} ${rsyncArgs} pvv@smtp.pvv.ntnu.no:/etc/group ${inputDir}/"
(let
args = lib.cli.toCommandLineShellGNU { } {
passwd-file = "${inputDir}/passwd";
group-file = "${inputDir}/group";
output-dir = wipDir;
shadow-file = pkgs.emptyFile;
output-passwd = true;
ignore-user-file = toString ./ignore_user_file.txt;
ignore-group-file = toString ./ignore_group_file.txt;
};
in ''${lib.getExe pkgs.passwd2systemd-users} ${args}'')
"${lib.getExe' pkgs.coreutils "shred"} -u ${inputDir}/passwd ${inputDir}/group"
":${lib.getExe pkgs.gnused} -i '$ a\\\\root:x:0:0:System administrator:/root:/run/current-system/sw/bin/bash' ${wipDir}/passwd"
":${lib.getExe pkgs.gnused} -i '$ a\\\\wwwrun:x:54:54:Apache httpd user:/var/empty:/run/current-system/sw/bin/bash' ${wipDir}/passwd"
":${lib.getExe pkgs.gnused} -i '$ a\\\\root:x:0:' ${wipDir}/group"
":${lib.getExe pkgs.gnused} -i '$ a\\\\wwwrun:x:54:' ${wipDir}/group"
"+${lib.getExe' pkgs.coreutils "install"} -m644 -o root -g root -t '${outputDir}' ${wipDir}/passwd ${wipDir}/group"
"${lib.getExe' pkgs.coreutils "shred"} -u ${wipDir}/passwd ${wipDir}/group"
];
AmbientCapabilities = [ "" ];
CapabilityBoundingSet = [ "" ];
DeviceAllow = [ "" ];
LockPersonality = true;
MemoryDenyWriteExecute = true;
PrivateDevices = true;
PrivateTmp = true;
PrivateUsers = true;
ProcSubset = "pid";
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = "tmpfs";
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectProc = "invisible";
ProtectSystem = "strict";
ProtectKernelTunables = true;
RemoveIPC = true;
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_UNIX"
];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SocketBindDeny = "any";
SystemCallArchitectures = "native";
SystemCallFilter = [
"@system-service"
"~@resources"
];
UMask = "0077";
IPAddressAllow = [
values.hosts.microbel.ipv4
values.hosts.microbel.ipv6
];
IPAddressDeny = "any";
RootDirectory = "/run/httpd-passwd-sync/root-mnt";
MountAPIVFS = true;
RuntimeDirectoryMode = "0750";
RuntimeDirectory = [
"httpd-passwd-sync/root-mnt"
"httpd-passwd-sync/in"
"httpd-passwd-sync/wip"
];
BindPaths = [
"/var/lib/httpd-passwd-sync"
];
BindReadOnlyPaths = [
builtins.storeDir
"/etc"
"/var/run/nscd"
];
};
};
};
}
+1
View File
@@ -13,6 +13,7 @@
fileSystems."/" =
{ device = "/dev/disk/by-uuid/4e8ecdd2-d453-4fff-b952-f06da00f3b85";
fsType = "ext4";
options = [ "relatime" ];
};
swapDevices = [ {
+198
View File
@@ -0,0 +1,198 @@
{
config,
pkgs,
lib,
...
}:
let
cfg = config.services.drumknotty;
in
{
imports = [
./dibbler.nix
./worblehat.nix
];
options.services.drumknotty = {
enable = lib.mkEnableOption "DrumknoTTY";
kioskMode = lib.mkEnableOption "" // {
description = ''
Whether to let dibbler take over the entire machine.
This will restrict the machine to a single TTY and make the program unquittable.
You can still get access to PTYs via SSH and similar, if enabled.
'';
};
screen = {
package = lib.mkPackageOption pkgs "screen" { };
sessionName = lib.mkOption {
type = lib.types.str;
default = "drumknotty";
example = "myscreensessionname";
description = ''
Sets the screen session name.
'';
};
limitHeight = lib.mkOption {
type = with lib.types; nullOr ints.unsigned;
default = null;
example = 42;
description = ''
If set, limits the height of the screen dibbler uses to the given number of lines.
'';
};
limitWidth = lib.mkOption {
type = with lib.types; nullOr ints.unsigned;
default = null;
example = 80;
description = ''
If set, limits the width of the screen dibbler uses to the given number of columns.
'';
};
};
};
config = lib.mkIf cfg.enable {
assertions = [
{
assertion = cfg.enable -> lib.any (b: b) [
cfg.dibbler.enable
cfg.worblehat.enable
];
message = "DrumknoTTY must have at least one service enabled";
}
];
users = {
users.drumknotty = {
group = "drumknotty";
extraGroups = [ "lp" ];
isNormalUser = true;
# TODO: make this display the error log or error message in case that
# the screen session service is bootlooping or otherwise off.
shell =
lib.mkIf cfg.kioskMode
(pkgs.writeShellScriptBin "login-shell"
"${lib.getExe' cfg.screen.package "screen"} -x ${cfg.screen.sessionName} -p dibbler"
// {
shellPath = "/bin/login-shell";
});
};
groups.drumknotty = { };
};
boot.kernelParams = lib.mkIf cfg.kioskMode [
"console=tty1"
];
services.getty.autologinUser = lib.mkIf cfg.kioskMode "drumknotty";
systemd.services.drumknotty-screen-session = lib.mkIf cfg.kioskMode {
description = "Drumknotty Screen Session";
wantedBy = [
"default.target"
];
after =
# TODO: this could be refined
if (cfg.dibbler.createLocalDatabase || cfg.worblehat.createLocalDatabase) then
[
"postgresql.service"
"dibbler-setup-database.service"
"worblehat-setup-database.service"
]
else
[
"network.target"
];
serviceConfig = {
Type = "forking";
RemainAfterExit = false;
Restart = "always";
RestartSec = "5s";
SuccessExitStatus = 1;
User = "drumknotty";
Group = "drumknotty";
ExecStartPre =
let
screenArgs = lib.escapeShellArgs [
# Send the specified command to a running screen session
"-X"
# Session name
"-S"
"${cfg.screen.sessionName}"
"kill"
];
in
"-${lib.getExe' cfg.screen.package "screen"} ${screenArgs}";
ExecStart =
let
screenrc = let
convertToFile = lines: lib.pipe lines [
lib.concatLists
(lib.concatStringsSep "\n")
(pkgs.writeText "drumknotty-screenrc")
];
in convertToFile [
(lib.optionals (cfg.screen.limitWidth != null) [
"screen width ${toString cfg.screen.limitWidth}"
])
(lib.optionals (cfg.screen.limitHeight != null) [
"screen height ${toString cfg.screen.limitHeight}"
])
(let
dibblerArgs = lib.cli.toCommandLineShellGNU { } {
config = "/etc/dibbler/dibbler.toml";
};
in lib.optionals cfg.dibbler.enable [
"screen -t dibbler ${lib.getExe cfg.dibbler.package} ${dibblerArgs} loop"
])
(let
worblehatArgs = lib.cli.toCommandLineShellGNU { } {
config = "/etc/worblehat/config.toml";
};
in lib.optionals cfg.worblehat.enable [
"screen -t worblehat ${lib.getExe cfg.worblehat.package} ${worblehatArgs} cli"
])
[ "select 0" ]
];
screenArgs = lib.escapeShellArgs [
# -dm creates the screen in detached mode without accessing it
"-dm"
# Session name
"-S"
"${cfg.screen.sessionName}"
# Set optimal output mode instead of VT100 emulation
"-O"
# Enable login mode, updates utmp entries
"-l"
# Config file path
"-c"
"${screenrc}"
];
in
"${lib.getExe' cfg.screen.package "screen"} ${screenArgs}";
};
};
};
}
+113
View File
@@ -0,0 +1,113 @@
{
config,
pkgs,
lib,
...
}:
let
mainCfg = config.services.drumknotty;
cfg = config.services.drumknotty.dibbler;
format = pkgs.formats.toml { };
in
{
options.services.drumknotty.dibbler = {
enable = lib.mkEnableOption "";
package = lib.mkPackageOption pkgs "dibbler" { };
settings = lib.mkOption {
description = "Configuration for dibbler";
default = { };
type = lib.types.submodule {
freeformType = format.type;
};
};
createLocalDatabase = lib.mkEnableOption "" // {
description = ''
Whether to set up a local postgres database automatically.
::: {.note}
You must set up postgres manually before enabling this option.
:::
'';
};
};
config = lib.mkIf (mainCfg.enable && cfg.enable) {
assertions = [
{
assertion = cfg.createLocalDatabase -> config.services.postgresql.enable;
message = "PostgreSQL must be enabled for dibbler to create a local database";
}
];
environment.systemPackages = [ cfg.package ];
environment.etc."dibbler/dibbler.toml".source = format.generate "dibbler.toml" cfg.settings;
services.drumknotty.dibbler.settings = {
limits = {
low_credit_warning_limit = lib.mkDefault (-100);
user_recent_transaction_limit = lib.mkDefault 100;
};
printer = {
label_type = lib.mkDefault "62";
label_rotate = lib.mkDefault false;
};
database = {
type = lib.mkIf cfg.createLocalDatabase "postgresql";
postgresql = {
username = lib.mkDefault "dibbler";
dbname = lib.mkDefault "dibbler";
host = lib.mkIf cfg.createLocalDatabase "/run/postgresql";
};
};
};
services.drumknotty.dibbler.settings.general = lib.mkIf mainCfg.kioskMode {
quit_allowed = false;
stop_allowed = false;
};
services.postgresql = lib.mkIf cfg.createLocalDatabase {
authentication = ''
local ${cfg.settings.database.postgresql.dbname} ${cfg.settings.database.postgresql.username} peer map=${cfg.settings.database.postgresql.username}
'';
identMap = ''
${cfg.settings.database.postgresql.username} drumknotty ${cfg.settings.database.postgresql.username}
'';
ensureDatabases = [ cfg.settings.database.postgresql.dbname ];
ensureUsers = [{
name = cfg.settings.database.postgresql.username;
ensureDBOwnership = true;
ensureClauses.login = true;
}];
};
systemd.services.dibbler-setup-database = lib.mkIf cfg.createLocalDatabase {
description = "Dibbler database setup";
wantedBy = [ "default.target" ];
requiredBy = [ "drumknotty-screen-session.service" ];
before = [ "drumknotty-screen-session.service" ];
after = [ "postgresql.service" ];
unitConfig = {
ConditionPathExists = "!/var/lib/dibbler/.db-setup-done";
};
serviceConfig = {
Type = "oneshot";
ExecStart = "${lib.getExe cfg.package} --config /etc/dibbler/dibbler.toml create-db";
ExecStartPost = "${lib.getExe' pkgs.coreutils "touch"} /var/lib/dibbler/.db-setup-done";
StateDirectory = "dibbler";
User = "drumknotty";
Group = "drumknotty";
};
};
};
}
+209
View File
@@ -0,0 +1,209 @@
{
config,
pkgs,
lib,
...
}:
let
mainCfg = config.services.drumknotty;
cfg = config.services.drumknotty.worblehat;
format = pkgs.formats.toml { };
in
{
options.services.drumknotty.worblehat = {
enable = lib.mkEnableOption "";
package = lib.mkPackageOption pkgs "worblehat" { };
settings = lib.mkOption {
description = "Configuration for worblehat";
default = { };
type = lib.types.submodule {
freeformType = format.type;
};
};
createLocalDatabase = lib.mkEnableOption "" // {
description = ''
Whether to set up a local postgres database automatically.
::: {.note}
You must set up postgres manually before enabling this option.
:::
'';
};
deadline-daemon = {
enable = lib.mkEnableOption "" // {
description = ''
Whether to enable the worblehat deadline-daemon service,
which periodically checks for upcoming deadlines and notifies users.
Note that this service is independent of the main worblehat service,
and must be enabled separately.
'';
};
onCalendar = lib.mkOption {
type = lib.types.str;
description = ''
How often to trigger rendering the map,
in the format of a systemd timer onCalendar configuration.
See {manpage}`systemd.timer(5)`.
'';
default = "*-*-* 10:15:00";
};
};
};
config = lib.mkMerge [
{
assertions = [
{
assertion = cfg.createLocalDatabase -> config.services.postgresql.enable;
message = "PostgreSQL must be enabled for worblehat to create a local database";
}
];
# TODO: Retrieve defaults from the example config file in the project code.
services.drumknotty.worblehat.settings = {
logging = {
debug = lib.mkDefault true;
debug_sql = lib.mkDefault false;
};
database = {
type = lib.mkDefault "sqlite";
sqlite.path = lib.mkDefault "./worblehat.sqlite";
postgresql = {
host = lib.mkDefault "localhost";
port = lib.mkDefault 5432;
username = lib.mkDefault "worblehat";
password = lib.mkDefault "/var/lib/worblehat/db-password";
database = lib.mkDefault "worblehat";
};
};
flask = {
TESTING = lib.mkDefault true;
DEBUG = lib.mkDefault true;
FLASK_ENV = lib.mkDefault "development";
SECRET_KEY = lib.mkDefault "change-me";
};
smtp = {
enabled = lib.mkDefault false;
host = lib.mkDefault "smtp.pvv.ntnu.no";
port = lib.mkDefault 587;
username = lib.mkDefault "worblehat";
password = lib.mkDefault "/var/lib/worblehat/smtp-password";
from = lib.mkDefault "worblehat@pvv.ntnu.no";
subject_prefix = lib.mkDefault "[Worblehat]";
};
deadline_daemon = {
enabled = lib.mkDefault true;
dryrun = lib.mkDefault false;
warn_days_before_borrowing_deadline = lib.mkDefault [
5
1
];
days_before_queue_position_expires = lib.mkDefault 14;
warn_days_before_expiring_queue_position_deadline = lib.mkDefault [
3
1
];
};
};
}
(lib.mkIf ((mainCfg.enable && cfg.enable) || cfg.deadline-daemon.enable) {
environment.systemPackages = [ cfg.package ];
environment.etc."worblehat/config.toml".source = format.generate "worblehat-config.toml" cfg.settings;
})
(lib.mkIf (mainCfg.enable && cfg.enable) {
services.drumknotty.worblehat.settings.general = lib.mkIf mainCfg.kioskMode {
quit_allowed = false;
stop_allowed = false;
};
services.drumknotty.worblehat.settings.database = lib.mkIf cfg.createLocalDatabase {
type = "postgresql";
postgresql.host = "/run/postgresql";
};
services.postgresql = lib.mkIf cfg.createLocalDatabase {
authentication = ''
local ${cfg.settings.database.postgresql.database} ${cfg.settings.database.postgresql.username} peer map=${cfg.settings.database.postgresql.username}
'';
identMap = ''
${cfg.settings.database.postgresql.username} drumknotty ${cfg.settings.database.postgresql.username}
'';
ensureDatabases = [ cfg.settings.database.postgresql.database ];
ensureUsers = [{
name = cfg.settings.database.postgresql.username;
ensureDBOwnership = true;
ensureClauses.login = true;
}];
};
systemd.services.worblehat-setup-database = lib.mkIf cfg.createLocalDatabase {
description = "Worblehat database setup";
wantedBy = [ "default.target" ];
requiredBy = [ "drumknotty-screen-session.service" ];
before = [ "drumknotty-screen-session.service" ];
after = [ "postgresql.service" ];
unitConfig = {
ConditionPathExists = "!/var/lib/worblehat/.db-setup-done";
};
serviceConfig = {
Type = "oneshot";
ExecStart = "${lib.getExe cfg.package} --config /etc/worblehat/config.toml create-db";
ExecStartPost = "${lib.getExe' pkgs.coreutils "touch"} /var/lib/worblehat/.db-setup-done";
StateDirectory = "worblehat";
User = "drumknotty";
Group = "drumknotty";
};
};
})
(lib.mkIf cfg.deadline-daemon.enable {
systemd.timers.worblehat-deadline-daemon = lib.mkIf cfg.deadline-daemon.enable {
description = "Worblehat Deadline Daemon";
wantedBy = [ "timers.target" ];
timerConfig = {
OnCalendar = cfg.deadline-daemon.onCalendar;
Persistent = true;
};
};
systemd.services.worblehat-deadline-daemon = lib.mkIf cfg.deadline-daemon.enable {
description = "Worblehat Deadline Daemon";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
serviceConfig = {
Type = "oneshot";
CPUSchedulingPolicy = "idle";
IOSchedulingClass = "idle";
ExecStart =
let
worblehatArgs = lib.cli.toCommandLineShellGNU { } {
config = "/etc/worblehat/config.toml";
};
in
"${lib.getExe cfg.package} ${worblehatArgs} deadline-daemon";
User = "drumknotty";
Group = "drumknotty";
};
};
})
];
}
+4 -4
View File
@@ -10,18 +10,18 @@ let
in
buildNpmPackage {
pname = "delete-your-element";
version = "3.5.1";
version = "3.6.0";
src = fetchFromGitea {
domain = "git.pvv.ntnu.no";
owner = "Drift";
repo = "delete-your-element";
rev = "80ac1d9d79207b6327975a264fcd9747b99a2a5d";
hash = "sha256-fcBpUZ+WEMUXyyo/uaArl4D1NJmK95isWqhFSt6HzUU=";
rev = "44fb6a02d3139e8ab10e9660ad931e5e70d1205f";
hash = "sha256-wDQhPbxwdkAm0kPhaDNjbk8rVFxnGinffVdASdFrYnU=";
};
inherit nodejs;
npmDepsHash = "sha256-EYxJi6ObJQOLyiJq4C3mV6I62ns9l64ZHcdoQxmN5Ao=";
npmDepsHash = "sha256-h1mmE0/+Y7SBwnI0vaYvV+KqRDJGzwJvDUOkigzHcOY=";
dontNpmBuild = true;
nativeBuildInputs = [ makeWrapper ];
+21 -18
View File
@@ -1,10 +1,12 @@
dibbler:
postgresql:
password: ENC[AES256_GCM,data:3X9A3jOpFVRuBg0gRiCEsZVKfLI=,iv:XC7LBNUhALk9IEhItV8fO5p/m7VKL0REBY1W2IZt7G4=,tag:l18R7EhbOlucZHFQiEvpHw==,type:str]
password: ENC[AES256_GCM,data:ZeNKipcCB+z8QVGeg1iV3MUXqALjotVz,iv:xCtgOoe6Pkr6Cq3vL+T4L+GW1KAcgP/xUz3YbHs5bCc=,tag:/X5phRYDAws8Aam1j+UaTw==,type:str]
worblehat:
postgresql:
password: ENC[AES256_GCM,data:WpJR6MumY+7WUYdVVgAqv1af+NmqecTMO9aP5lidSpE=,iv:7aoN8mjXckd81LxasMSG3R2vqj0SvzSl7wrEQ1LwToo=,tag:zeeNcEpkYnqyd8be0ZS+kQ==,type:str]
sops:
age:
- recipient: age1hzkvnktkr8t5gvtq0ccw69e44z5z6wf00n3xhk3hj24emf07je5s6q2evr
enc: |
- enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAvTk5YU3Z2Yy9HS1R4ME5I
UU1PRWVncHJYcXY5RlFpOWVQUWZsdy93ZDFBCnlxWkpaL1g5WmNSckNYd202WE40
@@ -12,8 +14,8 @@ sops:
ZnllQzJiK1ZkRmFndmtYdW9IclFWY1EK82f1iGt3nt8dJnEQlMujNqConf6Qq6GX
hqoqPoc2EM4kun28Bbpq4pAY7eEPRrWFqOkjYVvgIRoS88D7xT3LWg==
-----END AGE ENCRYPTED FILE-----
- recipient: age1ug30gg4y7ftuya0wdv7q0vh4egn00wlv2th7mt7cgc2ze46wmvyq9lq6ge
enc: |
recipient: age1hzkvnktkr8t5gvtq0ccw69e44z5z6wf00n3xhk3hj24emf07je5s6q2evr
- enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA5WTJIOUcxRlBuNmRrNUZo
MXFxeVJBTEhDK00yTUw1U2dHckNFYWZKWkhNCnYxYmtrUEVvd1RaYUI5WTRTRW16
@@ -21,8 +23,8 @@ sops:
eTB4WldMNW9GNUwwaEUzRThsemxRVzQKGpa0J2PBzDRdHijm0e3nFAaxQCHUjz+L
KataXJEMCijJ6k+7vpb5QMxe2jB1J2PMxNGFp0bWAy2Al3p/Ez2Kww==
-----END AGE ENCRYPTED FILE-----
- recipient: age1mrnldl334l2nszuta6ywvewng0fswv2dz9l5g4qcwe3nj4yxf92qjskdx6
enc: |
recipient: age1ug30gg4y7ftuya0wdv7q0vh4egn00wlv2th7mt7cgc2ze46wmvyq9lq6ge
- enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBZaW1ZSXhVeFVTQW9WYzVh
WkVUM2JkOU5VNU9oQXE2Y2pvcFlOWTdvbnpJClduS0RHL2xja291a2doQ0wzbzhQ
@@ -30,8 +32,8 @@ sops:
ZUdnS2RvOXI1dGNYQTl6ZHE1cUdMWHMK4ycAJQLyKCgJIzjQ02bPjz4Ct9eO6ivw
kfWhyMaoWwM9PhFcwSak0cLpX0C/IOzSzO78pf3WhG16pV7aXapdog==
-----END AGE ENCRYPTED FILE-----
- recipient: age1hmpdk4h69wxpwqk9tkud39f66hprhehxtzhgw97r6dvr7v0mx5jscsuhkn
enc: |
recipient: age1mrnldl334l2nszuta6ywvewng0fswv2dz9l5g4qcwe3nj4yxf92qjskdx6
- enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqaml0OVlhcUJSU1hSY3lP
bkM0cUV4Z2ZLeERHZ3BUNExuYS9KSU5CekQ4CmQ3SE1vdDBtdFJ6czZYR3U5Tk1X
@@ -39,8 +41,8 @@ sops:
Sy9XbjhwOFR6SFpaNHZLd3ZxdmxOVUEKBBbGmdVVlKHxO+/iODznLP3+dJGppybW
+1k9uenVHzie+pDKcrQpSyX2WDnmgg7hUAUiXPuz1eEWmwbRJnU/5w==
-----END AGE ENCRYPTED FILE-----
- recipient: age1wrssr4z4g6vl3fd3qme5cewchmmhm0j2xe6wf2meu4r6ycn37anse98mfs
enc: |
recipient: age1hmpdk4h69wxpwqk9tkud39f66hprhehxtzhgw97r6dvr7v0mx5jscsuhkn
- enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXK01vOVV5YlhsZ2ljYS91
OUVEaEpTbXFKOHVNVDVoMTlrS05wRmsyM2dvCjZHOXlCUGowd0J4UlQzSzM5dWJ0
@@ -48,8 +50,8 @@ sops:
RUR6Yi9SUDFCUkZmRk5hYTVFeGloZXcKY/XtaSoW8Pu2wS4oistLSc0T5JvMnt+w
s3yfe/zx9/1K6OtbeljF9FZVOB/dOamvk+Qlfl0T5qush7/WgGzErA==
-----END AGE ENCRYPTED FILE-----
- recipient: age1zhxul786an743u0fascv4wtc5xduu7qfy803lfs539yzhgmlq5ds2lznt5
enc: |
recipient: age1wrssr4z4g6vl3fd3qme5cewchmmhm0j2xe6wf2meu4r6ycn37anse98mfs
- enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBOM0pFb2tRTURtWmp6elRN
M0xtajlzMTNPMnppcGhJMVlsNHdwWmNGbFVFCnlxM1JQTkR2elAvdytKUEJ3djBS
@@ -57,8 +59,8 @@ sops:
eWlyWGhaS1JCNitUSVVScFk2WGEvOG8K2rpYPGx5jhyyRK4UkeJR96wDFr4Frzsr
QWz7fYZRWKWf0H0qn+bm9IfVJiBAlS5i16D1FnipZVmdWefFaZSEPg==
-----END AGE ENCRYPTED FILE-----
- recipient: age1sqs7urnzsdy64efmd0zukzv3gs5pnjksuxd7nqmdwdy5l0nqnunq6hyune
enc: |
recipient: age1zhxul786an743u0fascv4wtc5xduu7qfy803lfs539yzhgmlq5ds2lznt5
- enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBJVFV0WVZrK0wzbnhkcmcz
c2lIdVlKcFpoYjZIWlNPN0M5N2g2WG9YdlRJCjg5YlNoSzQ5YW5yRUVSeTEzRThY
@@ -66,8 +68,9 @@ sops:
MmxPMWNPYzJiOFRqY2VYczhvRm5IR3cKpUVV+zsMolsHI2YK9YqC6ecNT6QXv0TV
d1SpXRAexZBeWCCHBjSdvQBl8AT4EwrAIP2M2o++6i5DaGoGiEIWZQ==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-02-10T20:02:28Z"
mac: ENC[AES256_GCM,data:i8CjVxoD7zdkLNJlI9DCo/tDV5DUI7JdpozLtYZzI7Cu51GayaE2Y3Wg4de6P0L7C3FER04WfRe/h+G9PLZICX/CfSipQysyrEq3Pjt9IKsjytDhP9VYJ36QFGF0PuHUQAMSLts/tAoAvLue6MP+V82l5js9ghvyBrzyBGxoyJw=,iv:QFNxvCYxrSkwy7iT+2BEacNPftDXju1cibprVPDjic0=,tag:496E+oCy/VwTylyaWhQD+A==,type:str]
recipient: age1sqs7urnzsdy64efmd0zukzv3gs5pnjksuxd7nqmdwdy5l0nqnunq6hyune
lastmodified: "2026-06-07T08:58:13Z"
mac: ENC[AES256_GCM,data:rchs8pGkw7dthGOQNDB5p/kgQdfdystaC+jRr0bZnA4Q41+PVMu+vBSMIZ+9zZek6oENgchmV5rRS0CEeb9UQMMiPXCk2Q2jMaDfiNCmOmjf0YYeFWRM6g2lA+IZ3RgKjwhXa6i5JOeNrNewMjtx7MFcHTn3EBlg2mztyn1xbT0=,iv:0yvoFPDxpugaBmTtXSmhNz9XusJHrU3E02tBm1hVsZo=,tag:hZ1BzqfmlT1OhPSsn+CCTg==,type:str]
pgp:
- created_at: "2026-02-10T20:01:32Z"
enc: |-
@@ -90,4 +93,4 @@ sops:
-----END PGP MESSAGE-----
fp: F7D37890228A907440E1FD4846B9228E814A2AAC
unencrypted_suffix: _unencrypted
version: 3.11.0
version: 3.13.0
+3 -1
View File
@@ -1,4 +1,4 @@
{ pkgs, ... }:
{ pkgs, config, ... }:
{
users.users.vegardbm = {
isNormalUser = true;
@@ -8,12 +8,14 @@
"drift"
"nix-builder-users"
];
shell = if config.programs.zsh.enable then pkgs.zsh else pkgs.bash;
packages = with pkgs; [
btop
eza
];
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDVA3HqEx3je6L1AC+bP8sTxu3ZTKvTCR0npCyOVAYK5 vbm@arch-xeon"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICrYATHNvBNAHr9G+VwZIaAQPe02iRgAjqtZkW4x/dje vbm@talos"
];
};
}