From 49b169f4732d88f91376748b63d339f26a5469e1 Mon Sep 17 00:00:00 2001 From: Alexander Kobjolke Date: Thu, 12 Jun 2025 14:03:27 +0200 Subject: [PATCH] chore: Update inputs --- flake.lock | 60 +++++----- hosts/thrall/default.nix | 2 +- modules/nix-config.nix | 10 +- scripts/nixos-mailserver-migration-03.py | 142 +++++++++++++++++++++++ 4 files changed, 181 insertions(+), 33 deletions(-) create mode 100755 scripts/nixos-mailserver-migration-03.py diff --git a/flake.lock b/flake.lock index fa072e9..2ed5f4b 100644 --- a/flake.lock +++ b/flake.lock @@ -10,11 +10,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1747575206, - "narHash": "sha256-NwmAFuDUO/PFcgaGGr4j3ozG9Pe5hZ/ogitWhY+D81k=", + "lastModified": 1750173260, + "narHash": "sha256-9P1FziAwl5+3edkfFcr5HeGtQUtrSdk/MksX39GieoA=", "owner": "ryantm", "repo": "agenix", - "rev": "4835b1dc898959d8547a871ef484930675cb47f1", + "rev": "531beac616433bac6f9e2a19feb8e99a22a66baf", "type": "github" }, "original": { @@ -68,11 +68,11 @@ ] }, "locked": { - "lastModified": 1749436314, - "narHash": "sha256-CqmqU5FRg5AadtIkxwu8ulDSOSoIisUMZRLlcED3Q5w=", + "lastModified": 1751607816, + "narHash": "sha256-5PtrwjqCIJ4DKQhzYdm8RFePBuwb+yTzjV52wWoGSt4=", "owner": "nix-community", "repo": "disko", - "rev": "dfa4d1b9c39c0342ef133795127a3af14598017a", + "rev": "da6109c917b48abc1f76dd5c9bf3901c8c80f662", "type": "github" }, "original": { @@ -165,11 +165,11 @@ ] }, "locked": { - "lastModified": 1742649964, - "narHash": "sha256-DwOTp7nvfi8mRfuL1escHDXabVXFGT1VlPD1JHrtrco=", + "lastModified": 1749636823, + "narHash": "sha256-WUaIlOlPLyPgz9be7fqWJA5iG6rHcGRtLERSCfUDne4=", "owner": "cachix", "repo": "git-hooks.nix", - "rev": "dcf5072734cb576d2b0c59b2ac44f5050b5eac82", + "rev": "623c56286de5a3193aa38891a6991b28f9bab056", "type": "github" }, "original": { @@ -249,11 +249,11 @@ ] }, "locked": { - "lastModified": 1749628652, - "narHash": "sha256-f8jDF4G9m7pPySeQc6KskqMgtcJq6X1o2CytMx66qAE=", + "lastModified": 1751760902, + "narHash": "sha256-qBGNn7T/zOgUDQTo/RM/D2oxMkB2x36j3ajvpVanEVs=", "owner": "nix-community", "repo": "home-manager", - "rev": "450f06ec3cd0d86f67db58a7245db8848773e895", + "rev": "8b0180dde1d6f4cf632e046309e8f963924dfbd0", "type": "github" }, "original": { @@ -334,11 +334,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1749285348, - "narHash": "sha256-frdhQvPbmDYaScPFiCnfdh3B/Vh81Uuoo0w5TkWmmjU=", + "lastModified": 1751637120, + "narHash": "sha256-xVNy/XopSfIG9c46nRmPaKfH1Gn/56vQ8++xWA8itO4=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "3e3afe5174c561dee0df6f2c2b2236990146329f", + "rev": "5c724ed1388e53cc231ed98330a60eb2f7be4be3", "type": "github" }, "original": { @@ -350,11 +350,11 @@ }, "nixpkgs-25_05": { "locked": { - "lastModified": 1747610100, - "narHash": "sha256-rpR5ZPMkWzcnCcYYo3lScqfuzEw5Uyfh+R0EKZfroAc=", + "lastModified": 1749727998, + "narHash": "sha256-mHv/yeUbmL91/TvV95p+mBVahm9mdQMJoqaTVTALaFw=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "ca49c4304acf0973078db0a9d200fd2bae75676d", + "rev": "fd487183437963a59ba763c0cc4f27e3447dd6dd", "type": "github" }, "original": { @@ -414,11 +414,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1747179050, - "narHash": "sha256-qhFMmDkeJX9KJwr5H32f1r7Prs7XbQWtO0h3V0a0rFY=", + "lastModified": 1749285348, + "narHash": "sha256-frdhQvPbmDYaScPFiCnfdh3B/Vh81Uuoo0w5TkWmmjU=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "adaa24fbf46737f3f1b5497bf64bae750f82942e", + "rev": "3e3afe5174c561dee0df6f2c2b2236990146329f", "type": "github" }, "original": { @@ -491,11 +491,11 @@ ] }, "locked": { - "lastModified": 1749636823, - "narHash": "sha256-WUaIlOlPLyPgz9be7fqWJA5iG6rHcGRtLERSCfUDne4=", + "lastModified": 1750779888, + "narHash": "sha256-wibppH3g/E2lxU43ZQHC5yA/7kIKLGxVEnsnVK1BtRg=", "owner": "cachix", "repo": "pre-commit-hooks.nix", - "rev": "623c56286de5a3193aa38891a6991b28f9bab056", + "rev": "16ec914f6fb6f599ce988427d9d94efddf25fe6d", "type": "github" }, "original": { @@ -543,11 +543,11 @@ "nixpkgs-25_05": "nixpkgs-25_05" }, "locked": { - "lastModified": 1749244584, - "narHash": "sha256-BGmEptAyP2NrP4gX7VMYWo53h5e8r2iE/uo2+YPMcfo=", + "lastModified": 1751772161, + "narHash": "sha256-CxekYKL+M4VGb1pQk7lVDRe1x3th/5Du2xq/X1nNors=", "owner": "simple-nixos-mailserver", "repo": "nixos-mailserver", - "rev": "8b27add0883067e990bff4f847b6f7b6f53324b9", + "rev": "6004878dc6c1cb1c0cedd8c10a59d416c8dad9c9", "type": "gitlab" }, "original": { @@ -559,11 +559,11 @@ }, "stable": { "locked": { - "lastModified": 1749488106, - "narHash": "sha256-b9GIWdF/8jKpCC5JIMgDLZgwe8cEbty2fyTyo1eDFfI=", + "lastModified": 1751274312, + "narHash": "sha256-/bVBlRpECLVzjV19t5KMdMFWSwKLtb5RyXdjz3LJT+g=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "8fe3e32e7f210522377c3bcff80931a3284ace6a", + "rev": "50ab793786d9de88ee30ec4e4c24fb4236fc2674", "type": "github" }, "original": { diff --git a/hosts/thrall/default.nix b/hosts/thrall/default.nix index 9f567f1..0da18d6 100644 --- a/hosts/thrall/default.nix +++ b/hosts/thrall/default.nix @@ -412,7 +412,7 @@ in mailserver = { enable = true; - stateVersion = 1; + stateVersion = 3; fqdn = "thrall.failco.de"; domains = [ "failco.de" diff --git a/modules/nix-config.nix b/modules/nix-config.nix index 80b1825..09a5343 100644 --- a/modules/nix-config.nix +++ b/modules/nix-config.nix @@ -26,8 +26,14 @@ keep-outputs = true; keep-derivations = true; - trusted-substituters = [ "https://devenv.cachix.org" ]; - trusted-public-keys = [ "devenv.cachix.org-1:w1cLUi8dv3hnoSPGAuibQv+f9TZLr6cv/Hm9XgU50cw=" ]; + trusted-substituters = [ + "https://devenv.cachix.org" + "https://nixcache.reflex-frp.org" + ]; + trusted-public-keys = [ + "devenv.cachix.org-1:w1cLUi8dv3hnoSPGAuibQv+f9TZLr6cv/Hm9XgU50cw=" + "ryantrinkle.com-1:JJiAKaRv9mWgpVAz8dwewnZe0AzzEAzPkagE9SP5NWI=" + ]; trusted-users = [ "root" "alex" diff --git a/scripts/nixos-mailserver-migration-03.py b/scripts/nixos-mailserver-migration-03.py new file mode 100755 index 0000000..6aedad4 --- /dev/null +++ b/scripts/nixos-mailserver-migration-03.py @@ -0,0 +1,142 @@ +#!/usr/bin/env nix-shell +#!nix-shell -i python3 -p python3 + +import argparse +import os +import shutil +import sys +from enum import Enum +from pathlib import Path +from pwd import getpwnam + + +class FolderLayout(Enum): + Default = 1 + Folder = 2 + + +def check_user(vmail_root: Path): + owner = vmail_root.owner() + owner_uid = getpwnam(owner).pw_uid + + if os.geteuid() == owner_uid: + return + + try: + print( + f"Trying to switch effective user id to {owner_uid} ({owner})", + file=sys.stderr, + ) + os.seteuid(owner_uid) + return + except PermissionError: + print( + f"Failed switching to virtual mail user. Please run this script under it, for example by using `sudo -u {owner}`)", + file=sys.stderr, + ) + sys.exit(1) + + +def is_maildir_related(path: Path, layout: FolderLayout) -> bool: + if path.name in [ + "subscriptions" + # https://doc.dovecot.org/2.3/admin_manual/mailbox_formats/maildir/#imap-uid-mapping + "dovecot-uidlist", + # https://doc.dovecot.org/2.3/admin_manual/mailbox_formats/maildir/#imap-keywords + "dovecot-keywords", + ]: + return True + if not path.is_dir(): + return False + if path.name in ["cur", "new", "tmp"]: + return True + if layout is FolderLayout.Default and path.name.startswith("."): + return True + if layout is FolderLayout.Folder: + if path.name in ["mail"]: + return False + return True + + return False + + +def mkdir(dst: Path, dry_run: bool = True): + print(f'mkdir "{dst}"') + if not dry_run: + # u+rwx, setgid + dst.mkdir(mode=0o2700) + + +def move(src: Path, dst: Path, dry_run: bool = True): + print(f'mv "{src}" "{dst}"') + if not dry_run: + src.rename(dst) + + +def delete(dst: Path, dry_run: bool = True): + if not dst.exists(): + return + + if dst.is_dir(): + print(f'rm --recursive "{dst}"') + if not dry_run: + shutil.rmtree(dst) + else: + print(f'rm "{dst}"') + if not dry_run: + dst.unlink() + + +def main(vmail_root: Path, layout: FolderLayout, dry_run: bool = True): + maildirs = {path.parent for path in vmail_root.glob("*/*/cur")} + maybe_delete = [] + + # The old maildir will be the new home directory + for homedir in maildirs: + maildir = homedir / "mail" + mkdir(maildir, dry_run) + + for path in homedir.iterdir(): + if is_maildir_related(path, layout): + move(path, maildir / path.name, dry_run) + else: + maybe_delete.append(path) + + # Files that are part of the previous home directory, but now obsolete + for path in [ + vmail_root / ".dovecot.lda-dupes", + vmail_root / ".dovecot.lda-dupes.locks", + ]: + delete(path, dry_run) + + # The remaining files are likely obsolete, but should still be checked with care + for path in maybe_delete: + print(f"# rm {str(path)}") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description=""" + NixOS Mailserver Migration #3: Dovecot mail directory migration + (https://nixos-mailserver.readthedocs.io/en/latest/migrations.html#dovecot-mail-directory-migration) + """ + ) + parser.add_argument( + "vmail_root", type=Path, help="Path to the `mailserver.mailDirectory`" + ) + parser.add_argument( + "--layout", + choices=["default", "folder"], + required=True, + help="Folder layout: 'default' unless `mailserver.useFsLayout` was enabled, then'folder'", + ) + parser.add_argument( + "--execute", action="store_true", help="Actually perform changes" + ) + + args = parser.parse_args() + + layout = FolderLayout.Default if args.layout == "default" else FolderLayout.Folder + + check_user(args.vmail_root) + main(args.vmail_root, layout, not args.execute)