]> git.immae.eu Git - perso/Immae/Config/Nix.git/blob - modules/private/system/quatresaisons.nix
Add flake skeletons
[perso/Immae/Config/Nix.git] / modules / private / system / quatresaisons.nix
1 { config, pkgs, lib, flakes, ... }:
2 let
3 serverSpecificConfig = config.myEnv.serverSpecific.quatresaisons;
4 yarnModules = pkgs.yarn2nix-moretea.mkYarnModules rec {
5 name = "landing";
6 pname = name;
7 version = "v1.0.0";
8 packageJSON = "${pkgs.sources.webapps-landing}/package.json";
9 yarnLock = "${pkgs.sources.webapps-landing}/yarn.lock";
10 yarnNix = ../websites/tools/tools/landing/yarn-packages.nix;
11 };
12 toLanding = landingConfig: pkgs.stdenv.mkDerivation rec {
13 pname = "landing";
14 version = "v1.0.0";
15 src = pkgs.sources.webapps-landing;
16
17 buildInputs = [ yarnModules pkgs.yarn2nix-moretea.yarn ];
18 configurePhase = ''
19 ln -s ${yarnModules}/node_modules .
20 '';
21 buildPhase = ''
22 yarn build
23 '';
24 installPhase = ''
25 cp -a dist $out
26 cp -f ${landingConfig} $out/config.yml
27 ln -s service-worker.js $out/worker.js
28 '';
29 };
30 normalUsers = serverSpecificConfig.users;
31 userquotas = pkgs.writeScriptBin "user_quotas" ''
32 #!/usr/bin/env bash
33 set -euo pipefail
34
35 if [ `whoami` != "root" ]; then
36 list=$(id -u)
37 else
38 list="${builtins.concatStringsSep " " (lib.mapAttrsToList (n: v: builtins.toString v.uid) normalUsers)}"
39 fi
40
41 get_size () {
42 user=$1
43 home=$((du -sbx /home/$user 2>/dev/null | cut -d" " -f1) || echo 0)
44 nextcloud=$((du -sbx /home/var_lib/nextcloud/data/$user 2>/dev/null | cut -d" " -f1) || echo 0)
45 echo "Home: $(numfmt --to=iec "$home")"
46 echo "Nextcloud: $(numfmt --to=iec "$nextcloud")"
47 echo "Raw: $(($home + $nextcloud))"
48 }
49
50 for user in $list; do
51 group=$(id -ng "$user")
52 size=$(get_size "$group")
53 total=$(echo "$size" | grep ^Raw | cut -d" " -f2)
54 decomp=" $group: $(numfmt --to=iec "$total")"
55 decomp="$decomp;$(echo "$size" | grep -v ^Raw | sed -e "s/^/ /")"
56
57 sponsored=$(getent group $group | cut -d':' -f4)
58 IFS=","
59 for subuser in $sponsored; do
60 size=$(get_size "$subuser")
61 totalsub=$(echo "$size" | grep ^Raw | cut -d" " -f2)
62 total=$(($total + $totalsub))
63 decomp="$decomp; $subuser: $(numfmt --to=iec "$totalsub")"
64 decomp="$decomp;$(echo "$size" | grep -v ^Raw | sed -e "s/^/ /")"
65 done
66 echo "$group: $(numfmt --to=iec "$total")"
67 echo "$decomp" | tr ";" "\n"
68 done
69 '';
70 sponsoredUser = pkgs.writeScriptBin "sponsored_user" ''
71 #!/usr/bin/env bash
72
73 set -euo pipefail
74 [ -z "''${SUDO_USER+x}" ] && echo "Must be run with sudo" && exit 1
75
76 mygroup=$(id -ng $SUDO_USER)
77
78 sponsored=$(getent group $mygroup | cut -d':' -f4)
79
80 echo "Sponsored users: ''${sponsored:-<none>}"
81
82 log () {
83 touch /var/log/sponsored_users
84 chmod go-rwx /var/log/sponsored_users
85 echo "`date` $mygroup $1" | LANG=C cat -v | tr '\012' ' ' | sed 's:$:\x0a:' >> /var/log/sponsored_users
86 }
87
88 create_user () {
89 log "creates $1: $2"
90 useradd -m -G users,$mygroup -g $mygroup -p '!' "$1"
91 touch /var/lib/nixos/sponsored_users
92 chmod go-rwx /var/lib/nixos/sponsored_users
93 echo "$mygroup $1 $2" >> /var/lib/nixos/sponsored_users
94 (${pkgs.openldap}/bin/ldapadd -c -D cn=root,dc=salle-s,dc=org \
95 -y ${config.secrets.fullPaths."ldap/sync_password"} 2>/dev/null >/dev/null || true) <<EOF
96 dn: uid=$1,uid=$mygroup,ou=users,dc=salle-s,dc=org
97 objectClass: inetOrgPerson
98 cn: $1
99 description:: $(echo -n "$2" | base64)
100 sn: $1
101 uid: $1
102 EOF
103 while ! passwd "$1"; do
104 echo "please give an initial password"
105 done
106 }
107
108 delete_user () {
109 IFS=",";
110 for u in $sponsored; do
111 if [ "$u" = "$1" ]; then
112 log "deletes $1"
113 userdel -r "$1"
114 sed -i -e "/^$mygroup $1/d" /var/lib/nixos/sponsored_users
115 ${pkgs.openldap}/bin/ldapdelete -D cn=root,dc=salle-s,dc=org \
116 -y ${config.secrets.fullPaths."ldap/sync_password"} \
117 "uid=$1,uid=$mygroup,ou=users,dc=salle-s,dc=org"
118 echo "deleted"
119 exit 0
120 fi
121 done
122
123 echo "User does not exist or does not belong to you";
124 exit 1
125 }
126
127 reset_password () {
128 IFS=",";
129 for u in $sponsored; do
130 if [ "$u" = "$1" ]; then
131 log "resets password for $1"
132 passwd "$1"
133 exit 0
134 fi
135 done
136
137 echo "User does not exist or does not belong to you";
138 exit 1
139 }
140
141 reset_ldap_password () {
142 if [ "$1" = "$mygroup" ]; then
143 log "resets web password"
144 ${pkgs.openldap}/bin/ldappasswd -D cn=root,dc=salle-s,dc=org \
145 -y ${config.secrets.fullPaths."ldap/sync_password"} \
146 -S "uid=$mygroup,ou=users,dc=salle-s,dc=org"
147 else
148 IFS=",";
149 for u in $sponsored; do
150 if [ "$u" = "$1" ]; then
151 log "resets web password of $1"
152 ${pkgs.openldap}/bin/ldappasswd -D cn=root,dc=salle-s,dc=org \
153 -y ${config.secrets.fullPaths."ldap/sync_password"} \
154 -S "uid=$1,uid=$mygroup,ou=users,dc=salle-s,dc=org"
155 exit 0
156 fi
157 done
158
159 echo "User does not exist or does not belong to you";
160 exit 1
161 fi
162 }
163
164 show_help () {
165 echo "sponsored_users create username realname"
166 echo " create a new sub-user attached to your account"
167 echo "sponsored_users (delete|reset_password) username"
168 echo " delete a sub-user attached to your account or reset his password"
169 echo "sponsored_users reset_ldap_password username"
170 echo " reset the web password of a sub-user or yourself"
171 }
172
173 [ -z "''${1+x}" -o -z "''${2+x}" ] && { show_help ; exit 0; }
174 action="$1"
175 username="$2"
176 shift
177 shift
178
179 case "$action" in
180 create)
181 [ -z "''${1+x}" ] && { show_help ; echo "Conformément à la charte https://4c.salle-s.org/charte veuillez préciser le nom réel du futur utilisateur du compte $username, juste pour root." ; exit 1; }
182 create_user "$username" "$*";
183 ;;
184 delete)
185 delete_user "$username";
186 ;;
187 reset_password)
188 reset_password "$username";
189 ;;
190 reset_ldap_password)
191 reset_ldap_password "$username";
192 ;;
193 *)
194 show_help
195 ;;
196 esac
197 '';
198 in
199 {
200 deployment = {
201 targetUser = "root";
202 targetHost = lib.head config.hostEnv.ips.main.ip4;
203 };
204 # ssh-keyscan quatresaison | nix-shell -p ssh-to-age --run ssh-to-age
205 secrets.ageKeys = [ "age1yz8u6xvh2fltvyp96ep8crce3qx4tuceyhun6pwddfe0uvcrkarscxl7e7" ];
206
207 programs.ssh.package = pkgs.openssh.overrideAttrs(old: {
208 PATH_PASSWD_PROG = "/run/wrappers/bin/passwd";
209 buildFlags = [ "SSH_KEYSIGN=/run/wrappers/bin/ssh-keysign" ];
210 });
211 programs.ssh.extraConfig = ''
212 Host xdej-backup
213 User immae
214 IdentityFile /root/.ssh/id_ed25519_xdej
215 Hostname localhost
216 HostKeyAlias xdej-backup
217 Port 2218
218 ProxyJump quatra-for-xdej-backup
219 Host quatra-for-xdej-backup
220 User bouya
221 Hostname 4a.salle-s.org
222 IdentityFile /root/.ssh/id_ed25519_xdej
223 '';
224 programs.ssh.knownHosts."4a.salle-s.org" = {
225 publicKey = "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAmF4DB20bfD3DdobUtjo9UoGGw6OICJY0Nb+tQo+3bwXskwBhx+KLAK6K9YKBTh0IrWAbfgQN1K48YNz0Lr7SF8yig3/WynuS323USsU9a7QjdIZ5VIsXWxFICmbfaN3OnCNJ1r2FvBrOKj6hZ5uCRBFm1zFz/smefWIjq0XJIiE=";
226 };
227 programs.ssh.knownHosts."xdej-backup-rsa" = {
228 extraHostNames = [ "xdej-backup" ];
229 publicKey = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCw9oCbeDEz99snJurJmUDDwD6qo+3AZg0HvdwYaORKXG30yPYwX10Ficck/mVKE+P8q35m3p/udjNo9o074kNc6t8FtQKkdLeuPU8V1C83brZmFoOgOp/I+ApTglTf7MSztZDfAwtvLKcRVgxk0rQBH2hkyqijwwvG7sn1K4hhuW6ayFGgj8nvZUMweHP0NdChkO3KyJ43kCGGytx1oDPk+kedKDfEFVrRWDJIvdOY5tbq5n2e56t9wpuCLr+y6dyqBzUD4Y8kOhgy+THxJHIF1xkHvPRWVIe/ynt3kW11Ja51BYeLIF82yUgr7skS5Tbmg58LrO4Ojo7paobA24erCeJ31QVV4KbdVBw1qAB5FXXj8LSRfcE8DgLiANfZZim32PD8rrwVlOxZGxsx30BQ5G2fFhW55uygYsK2wXvuTVi+OFp/lYyBIPcviFs4Kp499AK0VG90Aq2ea0h5465NZOonXpOuHLgxNdCHtDAmWrLYBEiwwLB3+98zQO+o/xTWvMh6BjG+wa5aLo0X/yypOAEuGLYvwZCB1HEUIYk5sYMbNlXeRD1vi2Y6QjUpvufBoRY4T5khMskwupsAt02J9rMcLw29pZ5m2G5MRb7cDZDXqXUCOvKOd48ORIYuT6FunmhdakjmXe/9pHcX3AGJx6HJ7HJE9CDd04rzzRvGTQ==";
230 };
231 programs.ssh.knownHosts."xdej-backup-ed25519" = {
232 extraHostNames = [ "xdej-backup" ];
233 publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIN4vTu1P32+Kx+TpWG1G9WiOknjSyFEBCaE5nQInxG5j";
234 };
235
236 imports = builtins.attrValues (import ../.. flakes) ++
237 [ ./quatresaisons/nextcloud.nix ./quatresaisons/databases.nix ];
238
239 myEnv = import ../../../nixops/secrets/environment.nix;
240
241 fileSystems = {
242 "/" = { device = "/dev/disk/by-uuid/865931b4-c5cc-439f-8e42-8072c7a30634"; fsType = "ext4"; };
243 "/home" = { device = "/dev/disk/by-uuid/76020bc4-5b88-464c-8952-9a59072c597f"; fsType = "ext4"; neededForBoot = true; };
244 "/boot" = { device = "/dev/disk/by-uuid/0fb8421a-61e5-4ed5-a795-4dd3a9b2152a"; fsType = "ext4"; };
245 "/var/lib" = { device = "/home/var_lib"; fsType = "none"; options = [ "defaults,bind" ]; };
246 };
247 powerManagement.cpuFreqGovernor = "powersave";
248 hardware.enableRedistributableFirmware = true;
249
250 boot.initrd.availableKernelModules = [ "ahci" "megaraid_sas" "sd_mod" ];
251 boot.initrd.kernelModules = [ "dm-snapshot" ];
252 boot.kernelModules = [ "kvm-intel" ];
253
254 boot.loader.grub.enable = true;
255 boot.loader.grub.version = 2;
256 boot.loader.grub.device = "/dev/sda";
257
258 networking.firewall.enable = false;
259 networking.firewall.allowedTCPPorts = [ 80 443 ];
260 networking.useDHCP = false;
261 networking.interfaces.eth0.useDHCP = true;
262 networking.interfaces.eth0.ipv6.addresses = [
263 { address = pkgs.lib.head config.hostEnv.ips.main.ip6; prefixLength = 64; }
264 ];
265 networking.defaultGateway6 = { address = "fe80::1"; interface = "eth0"; };
266 services.udev.extraRules = ''
267 ACTION=="add", SUBSYSTEM=="net", ATTR{address}=="c8:60:00:8b:2f:f0", NAME="eth0"
268 '';
269 security.pam.services.chage.text = ''
270 auth sufficient pam_rootok.so
271 auth required pam_unix.so
272 account required pam_unix.so
273 session required pam_unix.so
274 password required pam_permit.so
275 '';
276 security.pam.services.sshd.makeHomeDir = true;
277 security.pam.services.passwd_default = {};
278 security.pam.services.passwd.text = ''
279 password required pam_cracklib.so enforce_for_root difok=2 minlen=8 dcredit=2 ocredit=2 retry=3
280 '' + config.security.pam.services.passwd_default.text;
281
282 system.activationScripts.ldapSync = {
283 deps = [ "secrets" "users" ];
284 text =
285 let
286 com = "-D cn=root,dc=salle-s,dc=org -y ${config.secrets.fullPaths."ldap/sync_password"}";
287 in ''
288 # Add users
289 ${pkgs.openldap}/bin/ldapadd -c ${com} -f ${config.secrets.fullPaths."ldap/ldaptree.ldif"} 2>/dev/null >/dev/null || true
290
291 # Remove obsolete users
292 ${pkgs.openldap}/bin/ldapsearch -LLL ${com} -s one -b "ou=users,dc=salle-s,dc=org" "uid" |\
293 grep "^uid" | ${pkgs.gnused}/bin/sed -e "s/uid: //" | while read ldapuser; do
294
295 for user in ${builtins.concatStringsSep " " (builtins.attrNames normalUsers)}; do
296 if [ "$user" = "$ldapuser" ]; then
297 continue 2
298 fi
299 done
300 ${pkgs.openldap}/bin/ldapdelete -r ${com} uid=$ldapuser,ou=users,dc=salle-s,dc=org
301 done
302
303 # Subusers
304 if [ -f /var/lib/nixos/sponsored_users ]; then
305 cat /var/lib/nixos/sponsored_users | while read mainUser subUser name; do
306 (${pkgs.openldap}/bin/ldapadd -c ${com} 2>/dev/null >/dev/null || true) <<EOF
307 dn: uid=$subUser,uid=$mainUser,ou=users,dc=salle-s,dc=org
308 objectClass: inetOrgPerson
309 cn: $subUser
310 description:: $(echo -n "$name" | base64)
311 sn: $subUser
312 uid: $subUser
313 EOF
314 done
315 fi
316 '';
317 };
318
319 secrets.keys = {
320 "ldap/sync_password" = {
321 permissions = "0400";
322 text = serverSpecificConfig.ldap_sync_password;
323 };
324 "ldap/ldaptree.ldif" = {
325 permissions = "0400";
326 text = serverSpecificConfig.ldap_service_users
327 + (builtins.concatStringsSep "\n" (lib.mapAttrsToList (n: v: ''
328 dn: uid=${n},ou=users,dc=salle-s,dc=org
329 objectClass: inetOrgPerson
330 cn: ${n}
331 description: ${v._meta.name or n} ${v._meta.email}
332 sn: ${n}
333 uid: ${n}
334 '') normalUsers));
335 };
336 };
337
338 myServices.monitoring.enable = true;
339 myServices.certificates.enable = true;
340 users.mutableUsers = true;
341 system.stateVersion = "21.03";
342 programs.zsh.enable = true;
343
344 users.motd = ''
345 Bienvenue sur quatresaisons.salle-s.org !
346
347 * Charte :
348 https://4c.salle-s.org/charte
349 * Gérer les utilisateurs unix additionnels :
350 sudo sponsored_user -h
351 * Applications web :
352 * tableau de bord : https://4c.salle-s.org/
353 * nextcloud : https://nextcloud.4c.salle-s.org/
354 '';
355
356 users.groups =
357 lib.mapAttrs (n: v: { gid = v.uid; }) normalUsers
358 // { wwwrun = { gid = config.ids.gids.wwwrun; }; };
359 users.users =
360 let
361 defaultNormal = n: {
362 group = n;
363 extraGroups = [ "users" ];
364 isNormalUser = true;
365 };
366 in
367 lib.mapAttrs (n: v: defaultNormal n // (lib.filterAttrs (k: _: k != "_meta") v)) normalUsers
368 // {
369 sponsored-separator = {
370 uid = 10000;
371 group = "users";
372 home = "/var/empty";
373 extraGroups = [];
374 isNormalUser = true;
375 createHome = false;
376 };
377 wwwrun = {
378 group = "wwwrun";
379 description = "Apache httpd user";
380 uid = config.ids.uids.wwwrun;
381 extraGroups = [ "keys" ];
382 };
383 };
384
385 system.activationScripts.usersPost = {
386 deps = [ "users" "groups" ];
387 text = builtins.concatStringsSep "\n" (lib.mapAttrsToList (n: v: ''
388 if getent shadow "${n}" | grep -q '^${n}:${v.initialHashedPassword or "!"}:1'; then
389 chage -d 0 "${n}"
390 [ '${v.initialHashedPassword or "!"}' = '!' ] && passwd -d "${n}"
391 fi
392 '') normalUsers);
393 };
394 security.sudo.extraRules = [
395 {
396 commands = [
397 { command = "${sponsoredUser}/bin/sponsored_user"; options = [ "NOPASSWD" ]; }
398 { command = "/run/current-system/sw/bin/sponsored_user"; options = [ "NOPASSWD" ]; }
399 ];
400 users = builtins.attrNames normalUsers;
401 runAs = "root";
402 }
403 ];
404
405 environment.systemPackages = [
406 sponsoredUser userquotas
407 pkgs.git pkgs.vim pkgs.rsync pkgs.strace pkgs.home-manager
408 pkgs.inetutils pkgs.htop pkgs.iftop pkgs.bind.dnsutils pkgs.httpie
409 pkgs.iotop pkgs.whois pkgs.ngrep pkgs.tcpdump pkgs.wireshark-cli
410 pkgs.tcpflow pkgs.nmap pkgs.p0f pkgs.socat pkgs.lsof pkgs.psmisc
411 pkgs.openssl pkgs.wget pkgs.pv pkgs.smartmontools pkgs.youtube-dl
412 pkgs.unzip pkgs.octave pkgs.feh pkgs.xv pkgs.sshfs pkgs.gdb
413 pkgs.file pkgs.lynx pkgs.tmux pkgs.awesome pkgs.libreoffice
414 pkgs.evince pkgs.firefox pkgs.xcalib pkgs.python3 pkgs.python2
415 pkgs.xorg.xkbcomp pkgs.subversion pkgs.xclip pkgs.imagemagick
416 pkgs.bc pkgs.sox pkgs.zip pkgs.gnome3.gnome-screenshot
417 pkgs.datadog-process-agent
418 ];
419
420 services.websites.env.production = {
421 enable = true;
422 adminAddr = "httpd@immae.eu";
423 httpdName = "Prod";
424 modules = [ "http2" "deflate" "filter" ];
425 extraConfig = [
426 ''
427 LogFormat "%{Host}i:%p %h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combinedVhost
428 Protocols h2 http/1.1
429 AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript application/javascript
430 '' ];
431 ips =
432 let ips = config.hostEnv.ips.main;
433 in (ips.ip4 or []) ++ (ips.ip6 or []);
434
435 fallbackVhost = {
436 certName = "quatresaisons";
437 hosts = [ "quatresaisons.immae.eu" ];
438 root = pkgs.runCommand "empty" {} "mkdir $out && touch $out/index.html";
439 extraConfig = [ "DirectoryIndex index.html" ];
440 };
441 vhostConfs.salle-s = {
442 certName = "quatresaisons";
443 addToCerts = true;
444 hosts = [ "salle-s.org" ];
445 root = toLanding ./quatresaisons/landing.yml;
446 extraConfig = [
447 ''
448 <Directory ${toLanding ./quatresaisons/landing.yml}>
449 AllowOverride None
450 Require all granted
451 DirectoryIndex index.html
452 </Directory>
453 ''
454 ];
455 };
456 vhostConfs.tools = {
457 certName = "quatresaisons";
458 addToCerts = true;
459 hosts = [ "4c.salle-s.org" "quatresaisons.salle-s.org" "quatre-saisons.salle-s.org" ];
460 root = toLanding ./quatresaisons/landing_4c.yml;
461 extraConfig = [
462 ''
463 Alias /charte ${serverSpecificConfig.charte_path}
464 <Directory ${serverSpecificConfig.charte_path}>
465 AllowOverride None
466 Require all granted
467 DirectoryIndex index.html index.txt
468 </Directory>
469
470 <Directory ${toLanding ./quatresaisons/landing_4c.yml}>
471 AllowOverride None
472 Require all granted
473 DirectoryIndex index.html
474 </Directory>
475 ''
476 ];
477 };
478 };
479 system.activationScripts.httpd = ''
480 install -d -m 0750 -o wwwrun -g wwwrun /var/lib/php
481 install -d -m 0750 -o wwwrun -g wwwrun /var/lib/php/sessions
482 '';
483
484 services.cron.systemCronJobs = [
485 "0 22 * * * root ${./quatresaisons/xdej-backup.sh}"
486 ];
487 services.phpfpm = {
488 phpOptions = ''
489 session.save_path = "/var/lib/php/sessions"
490 post_max_size = 20M
491 ; 15 days (seconds)
492 session.gc_maxlifetime = 1296000
493 ; 30 days (minutes)
494 session.cache_expire = 43200
495 '';
496 settings = {
497 log_level = "notice";
498 };
499 };
500
501 }