diff options
| -rw-r--r-- | .sops.yaml | 11 | ||||
| -rw-r--r-- | adonis/aur/default.nix | 10 | ||||
| -rw-r--r-- | adonis/captiveportal/default.nix | 14 | ||||
| -rw-r--r-- | adonis/default.nix | 9 | ||||
| -rw-r--r-- | adonis/network.nix | 20 | ||||
| -rw-r--r-- | adonis/tor/default.nix | 45 | ||||
| -rw-r--r-- | adonis/tor/snowflake.nix | 19 | ||||
| -rw-r--r-- | aphrodite/default.nix | 9 | ||||
| -rw-r--r-- | aphrodite/devops/default.nix | 6 | ||||
| -rw-r--r-- | aphrodite/devops/docker.nix | 13 | ||||
| -rw-r--r-- | aphrodite/devops/git.nix | 95 | ||||
| -rw-r--r-- | aphrodite/mail/default.nix | 79 | ||||
| -rw-r--r-- | aphrodite/network.nix | 20 | ||||
| -rw-r--r-- | aphrodite/search/default.nix | 108 | ||||
| -rw-r--r-- | aphrodite/search/engines.nix | 166 | ||||
| -rw-r--r-- | common/default.nix | 24 | ||||
| -rw-r--r-- | common/disks.nix | 60 | ||||
| -rw-r--r-- | common/fail2ban.nix | 13 | ||||
| -rw-r--r-- | common/hardware.nix | 16 | ||||
| -rw-r--r-- | common/mail.nix | 37 | ||||
| -rw-r--r-- | common/network.nix | 19 | ||||
| -rw-r--r-- | common/nginx.nix | 55 | ||||
| -rw-r--r-- | common/nvim.lua | 27 | ||||
| -rw-r--r-- | common/nvim.nix | 14 | ||||
| -rw-r--r-- | common/options.nix | 78 | ||||
| -rw-r--r-- | common/ssh.nix | 19 | ||||
| -rw-r--r-- | flake.nix | 56 | ||||
| -rw-r--r-- | secrets.nix | 11 | ||||
| -rw-r--r-- | secrets.yaml | 41 |
29 files changed, 1094 insertions, 0 deletions
diff --git a/.sops.yaml b/.sops.yaml new file mode 100644 index 0000000..2cf1dce --- /dev/null +++ b/.sops.yaml @@ -0,0 +1,11 @@ +keys: + - &toufy age1jcl6pr27ne5qmnadh723lhlu0js5dnt050akvaxmhvapm3yz9yqqkpakxs + - &adonis age1ul48hz3d4n4xr6fpux8w20effwtw4533gczef74cz4dsryd9zuzskdwj0e + - &aphrodite age1ra6vn99y233pxrlpcwpuwl7vc8ma5y4ucg5jlkfylh6kudsxzehqznqmet +creation_rules: + - path_regex: secrets.yaml$ + key_groups: + - age: + - *toufy + - *adonis + - *aphrodite diff --git a/adonis/aur/default.nix b/adonis/aur/default.nix new file mode 100644 index 0000000..9236940 --- /dev/null +++ b/adonis/aur/default.nix @@ -0,0 +1,10 @@ +{config, ...}: let + aurDomain = "aur.${config.customOps.domain.fqdn}"; +in { + services.nginx.virtualHosts.${aurDomain} = { + root = "/var/www/${aurDomain}"; + locations."/builds".extraConfig = "autoindex on;"; + forceSSL = true; + enableACME = true; + }; +} diff --git a/adonis/captiveportal/default.nix b/adonis/captiveportal/default.nix new file mode 100644 index 0000000..bb54039 --- /dev/null +++ b/adonis/captiveportal/default.nix @@ -0,0 +1,14 @@ +{config, ...}: let + domain = config.customOps.domain.fqdn; +in { + services.nginx.virtualHosts."cpc.${domain}" = { + extraConfig = '' + access_log off; + error_log /dev/null; + ''; + locations."/".return = 204; + forceSSL = false; + addSSL = true; + enableACME = true; + }; +} diff --git a/adonis/default.nix b/adonis/default.nix new file mode 100644 index 0000000..d8b5c8e --- /dev/null +++ b/adonis/default.nix @@ -0,0 +1,9 @@ +{ + imports = [ + ./network.nix + ./aur + ./captiveportal + ./tor + ]; + system.stateVersion = "25.11"; +} diff --git a/adonis/network.nix b/adonis/network.nix new file mode 100644 index 0000000..df24aac --- /dev/null +++ b/adonis/network.nix @@ -0,0 +1,20 @@ +{ + networking = { + hostName = "adonis"; + interfaces.ens3.ipv6 = { + addresses = [ + { + address = "2a0a:4cc0:c1:cf00::10"; + prefixLength = 64; + } + ]; + routes = [ + { + address = "::"; + via = "fe80::1"; + prefixLength = 0; + } + ]; + }; + }; +} diff --git a/adonis/tor/default.nix b/adonis/tor/default.nix new file mode 100644 index 0000000..7fcf2b4 --- /dev/null +++ b/adonis/tor/default.nix @@ -0,0 +1,45 @@ +{config, ...}: let + owner = config.customOps.owner.username; + domain = config.customOps.domain.fqdn; +in { + imports = [./snowflake.nix]; + + services.tor = { + enable = true; + openFirewall = true; + enableGeoIP = false; + torsocks.enable = true; + client.enable = true; + + relay = { + enable = true; + role = "relay"; + }; + + settings = { + Nickname = "${owner}"; + ContactInfo = "admin.tor@${domain}"; + + ExitRelay = false; + + MaxAdvertisedBandwidth = "100 MB"; + BandWidthRate = "100 MB"; + RelayBandwidthRate = "100 MB"; + RelayBandwidthBurst = "100 MB"; + + CookieAuthentication = true; + AvoidDiskWrites = 1; + HardwareAccel = 0; + SafeLogging = 1; + NumCPUs = 4; + + ORPort = [ + 9001 + { + addr = "[2a0a:4cc0:c1:cf00::10]"; + port = 9030; + } + ]; + }; + }; +} diff --git a/adonis/tor/snowflake.nix b/adonis/tor/snowflake.nix new file mode 100644 index 0000000..5a1c4bb --- /dev/null +++ b/adonis/tor/snowflake.nix @@ -0,0 +1,19 @@ +{config, ...}: let + torDomain = "tor.${config.customOps.domain.fqdn}"; +in { + services.snowflake-proxy = { + enable = true; + capacity = 20; + extraFlags = ["-metrics"]; + }; + + services.nginx.virtualHosts.${torDomain} = { + root = "/var/www/${torDomain}"; + forceSSL = true; + enableACME = true; + locations."~ \\.php$".extraConfig = '' + fastcgi_pass unix:${config.services.phpfpm.pools.mypool.socket}; + fastcgi_index index.php; + ''; + }; +} diff --git a/aphrodite/default.nix b/aphrodite/default.nix new file mode 100644 index 0000000..fb4101c --- /dev/null +++ b/aphrodite/default.nix @@ -0,0 +1,9 @@ +{ + imports = [ + ./network.nix + ./devops + ./mail + ./search + ]; + system.stateVersion = "25.11"; +} diff --git a/aphrodite/devops/default.nix b/aphrodite/devops/default.nix new file mode 100644 index 0000000..74fb9aa --- /dev/null +++ b/aphrodite/devops/default.nix @@ -0,0 +1,6 @@ +{ + imports = [ + ./docker.nix + ./git.nix + ]; +} diff --git a/aphrodite/devops/docker.nix b/aphrodite/devops/docker.nix new file mode 100644 index 0000000..cb53fcb --- /dev/null +++ b/aphrodite/devops/docker.nix @@ -0,0 +1,13 @@ +{pkgs, ...}: { + environment.systemPackages = with pkgs; [ + docker-compose + ]; + virtualisation = { + docker = { + enable = true; + storageDriver = "btrfs"; + autoPrune.enable = true; + }; + oci-containers.backend = "docker"; + }; +} diff --git a/aphrodite/devops/git.nix b/aphrodite/devops/git.nix new file mode 100644 index 0000000..7b2800b --- /dev/null +++ b/aphrodite/devops/git.nix @@ -0,0 +1,95 @@ +{ + config, + pkgs, + ... +}: let + domain = "git.${config.customOps.domain.fqdn}"; + cgitPatched = pkgs.fetchpatch2 { + url = "https://git.zx2c4.com/cgit/patch/?id=601ba0f25d6d9df488a5a37c7877818ac47966b0"; + sha256 = "sha256-yW54g40Bj2QxUwj4KZUjHMT1JGvVKW7o16NM83XDqsQ="; + }; +in { + programs.git = { + enable = true; + lfs.enable = true; + config = { + init = { + defaultBranch = "main"; + }; + }; + }; + + services.gitolite = { + enable = true; + user = "git"; + group = "git"; + adminPubkey = config.customOps.owner.pubkey; + extraGitoliteRc = '' + %RC = ( + UMASK => 0027, + GIT_CONFIG_KEYS => '.*', + LOG_EXTRA => 1, + ROLES => { + READERS => 1, + WRITERS => 1, + }, + ENABLE => [ + 'help', + 'desc', + 'info', + 'perms', + 'writable', + 'ssh-authkeys', + 'git-config', + 'daemon', + 'gitweb', + ], + ); + ''; + }; + + services.cgit.${domain} = { + enable = true; + package = pkgs.cgit.overrideAttrs (old: { + patches = (old.patches or []) ++ [cgitPatched]; + }); + user = "git"; + group = "git"; + gitHttpBackend = { + enable = true; + checkExportOkFiles = true; + }; + scanPath = "${config.services.gitolite.dataDir}/repositories"; + settings = { + root-title = domain; + root-desc = "toufy's project repositories"; + snapshots = "tar.gz zip"; + clone-url = "https://${domain}/$CGIT_REPO_URL"; + enable-index-owner = true; + enable-index-links = true; + remove-suffix = true; + enable-blame = true; + enable-commit-graph = true; + enable-log-filecount = true; + enable-log-linecount = true; + strict-export = "git-daemon-export-ok"; + branch-sort = "age"; + virtual-root = "/"; + enable-git-config = true; + "mimetype.gif" = "image/gif"; + "mimetype.html" = "text/html"; + "mimetype.jpg" = "image/jpeg"; + "mimetype.jpeg" = "image/jpeg"; + "mimetype.pdf" = "application/pdf"; + "mimetype.png" = "image/png"; + "mimetype.svg" = "image/svg+xml"; + readme = ":README.md"; + project-list = "${config.services.gitolite.dataDir}/projects.list"; + }; + }; + + services.nginx.virtualHosts.${domain} = { + forceSSL = true; + enableACME = true; + }; +} diff --git a/aphrodite/mail/default.nix b/aphrodite/mail/default.nix new file mode 100644 index 0000000..c12e79d --- /dev/null +++ b/aphrodite/mail/default.nix @@ -0,0 +1,79 @@ +{ + config, + lib, + ... +}: let + domainFqdn = config.customOps.domain.fqdn; + hostname = config.networking.hostName; +in { + sops.secrets = let + accounts = config.customOps.mailAccounts; + in + builtins.listToAttrs ( + map (acc: { + name = accounts.${acc}.passwdFile; + value = {owner = "dovecot2";}; + }) (builtins.attrNames accounts) + ); + + mailserver = { + enable = true; + stateVersion = 3; + fqdn = "${hostname}.${domainFqdn}"; + domains = [domainFqdn]; + systemDomain = domainFqdn; + systemName = domainFqdn; + systemContact = "postmaster@${domainFqdn}"; + + dmarcReporting.enable = true; + tlsrpt.enable = true; + + fullTextSearch.enable = true; + virusScanning = true; + + mailboxes = { + Archive = { + auto = "subscribe"; + specialUse = "Archive"; + }; + Drafts = { + auto = "subscribe"; + specialUse = "Drafts"; + }; + Junk = { + auto = "subscribe"; + specialUse = "Junk"; + }; + Sent = { + auto = "subscribe"; + specialUse = "Sent"; + }; + Trash = { + auto = "subscribe"; + specialUse = "Trash"; + }; + }; + + loginAccounts = + lib.mapAttrs (account: cfg: { + aliases = cfg.aliases; + aliasesRegexp = cfg.aliasesRegex; + catchAll = cfg.catchAll; + hashedPasswordFile = config.sops.secrets.${cfg.passwdFile}.path; + sendOnly = cfg.sendOnly; + }) + config.customOps.mailAccounts; + certificateScheme = "acme"; + }; + + services.roundcube = { + enable = true; + hostName = "mail.${domainFqdn}"; + extraConfig = '' + $config['imap_host'] = "ssl://${hostname}.${domainFqdn}"; + $config['smtp_host'] = "ssl://${hostname}.${domainFqdn}"; + $config['smtp_user'] = "%u"; + $config['smtp_pass'] = "%p"; + ''; + }; +} diff --git a/aphrodite/network.nix b/aphrodite/network.nix new file mode 100644 index 0000000..a5fa512 --- /dev/null +++ b/aphrodite/network.nix @@ -0,0 +1,20 @@ +{ + networking = { + hostName = "aphrodite"; + interfaces.ens3.ipv6 = { + addresses = [ + { + address = "2a0a:4cc0:c0:2991::10"; + prefixLength = 64; + } + ]; + routes = [ + { + address = "::"; + via = "fe80::1"; + prefixLength = 0; + } + ]; + }; + }; +} diff --git a/aphrodite/search/default.nix b/aphrodite/search/default.nix new file mode 100644 index 0000000..172e8f8 --- /dev/null +++ b/aphrodite/search/default.nix @@ -0,0 +1,108 @@ +{config, ...}: let + searxDomain = "search.${config.customOps.domain.fqdn}"; +in { + imports = [./engines.nix]; + + sops.secrets.searx.owner = "searx"; + + services.searx = { + enable = true; + redisCreateLocally = true; + + limiterSettings = { + real_ip = { + x_for = 1; + ipv4_prefix = 32; + ipv6_prefix = 56; + }; + + botdetection = { + ip_limit = { + filter_link_local = true; + link_token = true; + }; + }; + }; + + faviconsSettings = { + favicons = { + cfg_schema = 1; + cache = { + db_url = "/var/cache/searx/faviconcache.db"; + HOLD_TIME = 5184000; + LIMIT_TOTAL_BYTES = 2147483648; + BLOB_MAX_BYTES = 40960; + MAINTENANCE_MODE = "auto"; + MAINTENANCE_PERIOD = 600; + }; + }; + }; + + settings = { + general = { + debug = false; + instance_name = "${config.customOps.owner.username}'s search"; + donation_url = false; + contact_url = false; + privacypolicy_url = false; + enable_metrics = false; + }; + + ui = { + static_use_hash = true; + default_locale = "en"; + query_in_title = false; + infinite_scroll = true; + center_alignment = false; + default_theme = "simple"; + theme_args.simple_style = "auto"; + search_on_category_select = true; + hotkeys = "vim"; + url_formatting = "full"; + }; + + search = { + safe_search = 0; + autocomplete_min = 2; + autocomplete = "duckduckgo"; + favicon_resolver = "allesedv"; + ban_time_on_fail = 5; + max_ban_time_on_fail = 120; + }; + + server = { + base_url = "https://${searxDomain}"; + port = 8888; + bind_address = "127.0.0.1"; + secret_key = config.sops.secrets.searx.path; + limiter = true; + public_instance = true; + image_proxy = false; + method = "POST"; + }; + + outgoing = { + request_timeout = 30.0; + pool_connections = 100; + pool_maxsize = 15; + enable_http2 = true; + }; + + enabled_plugins = [ + "Basic Calculator" + "Hash plugin" + "Tor check plugin" + "Open Access DOI rewrite" + "Hostnames plugin" + "Unit converter plugin" + "Tracker URL remover" + ]; + }; + }; + + services.nginx.virtualHosts.${searxDomain} = { + forceSSL = true; + enableACME = true; + locations."/".proxyPass = "http://localhost:8888"; + }; +} diff --git a/aphrodite/search/engines.nix b/aphrodite/search/engines.nix new file mode 100644 index 0000000..6b112de --- /dev/null +++ b/aphrodite/search/engines.nix @@ -0,0 +1,166 @@ +{lib, ...}: { + services.searx.settings.engines = + lib.mapAttrsToList ( + name: value: {inherit name;} // value + ) { + # unnecessary + "wiby".inactive = true; + "bandcamp".inactive = true; + "github".inactive = true; + "dictzone".inactive = true; + "lingva".inactive = true; + "mymemory translated".inactive = true; + "mozhi".inactive = true; + "presearch".inactive = true; + "presearch images".inactive = true; + "presearch videos".inactive = true; + "presearch news".inactive = true; + "seznam".inactive = true; + "goo".inactive = true; + "naver".inactive = true; + "naver videos".inactive = true; + "naver images".inactive = true; + "naver news".inactive = true; + "alexandria".inactive = true; + "ask".inactive = true; + "crowdview".inactive = true; + "mwmbl".inactive = true; + "searchmysite".inactive = true; + "stract".inactive = true; + "bpb".inactive = true; + "tagesschau".inactive = true; + "wikimini".inactive = true; + "findthatmeme".inactive = true; + "frinkiac".inactive = true; + "livespace".inactive = true; + "sepiasearch".inactive = true; + "mediathekviewweb".inactive = true; + "ina".inactive = true; + "niconio".inactive = true; + "acfun".inactive = true; + "iqiyi".inactive = true; + "wolframalpha".inactive = true; + "ansa".inactive = true; + "il post".inactive = true; + "deezer".inactive = true; + "habrahabr".inactive = true; + "btdigg".inactive = true; + "duden".inactive = true; + "woxikon.de synonyme".inactive = true; + "jisho".inactive = true; + "moviepilot".inactive = true; + "senscritique".inactive = true; + "geizhals".inactive = true; + "openmeteo".inactive = true; + "fyyd".inactive = true; + "yummly".inactive = true; + "chefkoch".inactive = true; + "destatis".inactive = true; + # big brother + "google play movies".inactive = true; + "google play apps".inactive = true; + "google news".inactive = true; + "google images".inactive = true; + "google videos".inactive = true; + "google scholar".inactive = true; + "youtube".inactive = true; + "bing images".inactive = true; + "bing videos".inactive = true; + "bing news".inactive = true; + "microsoft learn".inactive = true; + "material icons".inactive = true; + "apple maps".inactive = true; + "apple app store".inactive = true; + "goodreads".inactive = true; + # captcha + "cppreference".inactive = true; + "lib.rs".inactive = true; + "sourcehut".inactive = true; + "free software directory".inactive = true; + "searchcode code".inactive = true; + "pdbe".inactive = true; + "1337x".inactive = true; + "kickass".inactive = true; + "library genesis".inactive = true; + "openrepos".inactive = true; + "tokyotoshokan".inactive = true; + "duckduckgo images".inactive = true; + "duckduckgo videos".inactive = true; + "duckduckgo news".inactive = true; + "duckduckgo weather".inactive = true; + "mojeek images".inactive = true; + "mojeek news".inactive = true; + "qwant images".inactive = true; + "qwant videos".inactive = true; + "qwant news".inactive = true; + # non-free + "tineye".inactive = true; + "1x".inactive = true; + "adobe stock".inactive = true; + "adobe stock video".inactive = true; + "adobe stock audio".inactive = true; + "deviantart".inactive = true; + "flickr".inactive = true; + "imgur".inactive = true; + "library of congress".inactive = true; + "pinterest".inactive = true; + "unsplash".inactive = true; + "bilibili".inactive = true; + "dailymotion".inactive = true; + "vimeo".inactive = true; + "yahoo".inactive = true; + "yahoo news".inactive = true; + "genius".inactive = true; + "mixcloud".inactive = true; + "soundcloud".inactive = true; + "huggingface".inactive = true; + "huggingface datasets".inactive = true; + "huggingface spaces".inactive = true; + "9gag".inactive = true; + "reddit".inactive = true; + "imdb".inactive = true; + "rottentomatoes".inactive = true; + # shady + "right dao".inactive = true; + "quark".inactive = true; + "quark images".inactive = true; + "sogou".inactive = true; + "sogou images".inactive = true; + "sogou wechat".inactive = true; + "sogou videos".inactive = true; + # LLM + "cloudflareai".inactive = true; + "yacy".inactive = true; + "yacy images".inactive = true; + "yep".inactive = true; + "yep images".inactive = true; + "yep news".inactive = true; + "360search".inactive = true; + "360search videos".inactive = true; + "baidu".inactive = true; + "baidu images".inactive = true; + "baidu kaifa".inactive = true; + "seekr images".inactive = true; + "seekr news".inactive = true; + "seekr videos".inactive = true; + # censorship + "reuters".inactive = true; + # far-right/disinformation/misinformation + "bitchute".inactive = true; + "rumble".inactive = true; + # slow + "crossref".inactive = true; + "wikidata".inactive = true; + + # enabled - general + "mojeek".disabled = false; + "startpage".disabled = false; + "qwant".disabled = false; + "mulvaddelta".disabled = false; + "duckduckgo".disabled = false; + "mulvaddelta brave".disabled = false; + "brave".disabled = false; + "google".disabled = false; + "bing".disabled = false; + }; +} diff --git a/common/default.nix b/common/default.nix new file mode 100644 index 0000000..fca1d03 --- /dev/null +++ b/common/default.nix @@ -0,0 +1,24 @@ +{ + imports = [ + ./disks.nix + ./fail2ban.nix + ./hardware.nix + ./mail.nix + ./network.nix + ./nginx.nix + ./nvim.nix + ./options.nix + ./ssh.nix + ]; + boot.loader.systemd-boot.enable = true; + boot.loader.efi.canTouchEfiVariables = true; + nix.settings.experimental-features = ["nix-command" "flakes"]; + customOps.owner = { + username = "toufy"; + pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIN8CKkq7m/FPTqjAO3yGWhH7y+9flDjyNC9hQFenvbKs toufy"; + }; + customOps.domain = { + name = "toufy"; + tld = "me"; + }; +} diff --git a/common/disks.nix b/common/disks.nix new file mode 100644 index 0000000..6208bdc --- /dev/null +++ b/common/disks.nix @@ -0,0 +1,60 @@ +{ + disko.devices = { + disk = { + main = { + type = "disk"; + device = "/dev/vda"; + content = { + type = "gpt"; + partitions = { + ESP = { + priority = 1; + name = "ESP"; + start = "1M"; + end = "1G"; + type = "EF00"; + content = { + type = "filesystem"; + format = "vfat"; + mountpoint = "/boot"; + mountOptions = ["fmask=0022" "dmask=0022"]; + }; + }; + root = { + size = "100%"; + content = { + type = "btrfs"; + extraArgs = ["-f"]; + subvolumes = { + "/root" = { + mountOptions = ["compress=zstd"]; + mountpoint = "/"; + }; + "/home" = { + mountOptions = ["compress=zstd"]; + mountpoint = "/home"; + }; + "/nix" = { + mountOptions = [ + "compress=zstd" + "noatime" + ]; + mountpoint = "/nix"; + }; + "/swap" = { + mountpoint = "/swap"; + mountOptions = ["noatime"]; + swap.swapfile = { + size = "8G"; + path = "swapfile"; + }; + }; + }; + }; + }; + }; + }; + }; + }; + }; +} diff --git a/common/fail2ban.nix b/common/fail2ban.nix new file mode 100644 index 0000000..0670df5 --- /dev/null +++ b/common/fail2ban.nix @@ -0,0 +1,13 @@ +{ + services.fail2ban = { + enable = true; + maxretry = 5; + bantime = "24h"; + bantime-increment = { + enable = true; + formula = "ban.Time * math.exp(float(ban.Count+1)*banFactor)/math.exp(1*banFactor)"; + rndtime = "12h"; + overalljails = true; + }; + }; +} diff --git a/common/hardware.nix b/common/hardware.nix new file mode 100644 index 0000000..16db18b --- /dev/null +++ b/common/hardware.nix @@ -0,0 +1,16 @@ +{ + lib, + modulesPath, + ... +}: { + imports = [ + (modulesPath + "/profiles/qemu-guest.nix") + ]; + + boot.initrd.availableKernelModules = ["ata_piix" "uhci_hcd" "virtio_pci" "sr_mod" "virtio_blk"]; + boot.initrd.kernelModules = []; + boot.kernelModules = []; + boot.extraModulePackages = []; + + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; +} diff --git a/common/mail.nix b/common/mail.nix new file mode 100644 index 0000000..fce389c --- /dev/null +++ b/common/mail.nix @@ -0,0 +1,37 @@ +{config, ...}: let + domainFqdn = config.customOps.domain.fqdn; + domainName = config.customOps.domain.name; + domainTld = config.customOps.domain.tld; +in { + customOps.mailAccounts = { + "domain@${domainFqdn}" = { + passwdFile = "mailserver/domain"; + aliases = [ + "dmarc@${domainFqdn}" + "rua@${domainFqdn}" + "ruf@${domainFqdn}" + "caa@${domainFqdn}" + "tls@${domainFqdn}" + ]; + }; + "root@${domainFqdn}" = { + passwdFile = "mailserver/root"; + aliases = [ + "postmaster@${domainFqdn}" + "security@${domainFqdn}" + "abuse@${domainFqdn}" + "webmaster@${domainFqdn}" + "info@${domainFqdn}" + "support@${domainFqdn}" + ]; + aliasesRegex = [ + "/^admin\\..*@${domainName}\\.${domainTld}$/" + ]; + }; + "contact@${domainFqdn}" = { + passwdFile = "mailserver/contact"; + aliases = ["@${domainFqdn}"]; + catchAll = [domainFqdn]; + }; + }; +} diff --git a/common/network.nix b/common/network.nix new file mode 100644 index 0000000..c1d6eda --- /dev/null +++ b/common/network.nix @@ -0,0 +1,19 @@ +{config, ...}: let + customDomain = config.customOps.domain.fqdn; + hostname = config.networking.hostName; +in { + networking = { + enableIPv6 = true; + firewall = { + logRefusedPackets = true; + allowedTCPPorts = [80 443]; + }; + }; + + services.nginx.virtualHosts."${hostname}.${customDomain}" = { + default = true; + locations."/".return = 204; + forceSSL = true; + enableACME = true; + }; +} diff --git a/common/nginx.nix b/common/nginx.nix new file mode 100644 index 0000000..1c4a6f1 --- /dev/null +++ b/common/nginx.nix @@ -0,0 +1,55 @@ +{ + config, + pkgs, + ... +}: { + networking.firewall.allowedTCPPorts = [80 443]; + + services.nginx = { + enable = true; + package = pkgs.nginx.override { + modules = [ + pkgs.nginxModules.moreheaders + pkgs.nginxModules.brotli + ]; + }; + + recommendedTlsSettings = true; + recommendedOptimisation = true; + recommendedGzipSettings = true; + recommendedUwsgiSettings = true; + recommendedProxySettings = true; + recommendedBrotliSettings = true; + + sslCiphers = "EECDH+AESGCM:EECDH+CHACHA20:EDH+AESGCM:EDH+CHACHA20:AES256+EECDH:AES256+EDH:!aNULL"; + + appendHttpConfig = '' + map $scheme $hsts_header { + https "max-age=31536000; includeSubdomains; preload"; + } + more_set_headers 'Strict-Transport-Security: $hsts_header'; + more_set_headers 'Content-Security-Policy: upgrade-insecure-requests'; + more_set_headers 'Referrer-Policy: origin-when-cross-origin'; + more_set_headers 'X-Frame-Options: SAMEORIGIN'; + more_set_headers 'X-Content-Type-Options: nosniff'; + more_set_headers 'X-XSS-Protection: 0'; + ''; + }; + + services.phpfpm.pools.mypool = { + user = "nobody"; + settings = { + "pm" = "dynamic"; + "listen.owner" = config.services.nginx.user; + "pm.max_children" = 75; + "pm.start_servers" = 10; + "pm.min_spare_servers" = 5; + "pm.max_spare_servers" = 20; + }; + }; + + security.acme = { + acceptTerms = true; + defaults.email = "security@${config.customOps.domain.fqdn}"; + }; +} diff --git a/common/nvim.lua b/common/nvim.lua new file mode 100644 index 0000000..7b7c4dc --- /dev/null +++ b/common/nvim.lua @@ -0,0 +1,27 @@ +-- global +vim.g.mapleader = " " +vim.g.maplocalleader = "\\" + +-- opts +vim.opt.nu = true +vim.opt.relativenumber = true +vim.opt.shiftwidth = 4 +vim.opt.tabstop = 4 +vim.opt.softtabstop = 4 +vim.opt.expandtab = false +vim.opt.smartindent = true +vim.opt.wrap = false +vim.opt.hlsearch = false +vim.opt.incsearch = true +vim.opt.scrolloff = 6 +vim.opt.cursorline = true +vim.opt.cursorcolumn = true +vim.opt.mouse = nil +vim.opt.clipboard = "unnamedplus" +vim.opt.completeopt = { "menu", "menuone", "noselect" } +vim.opt.winborder = "rounded" + +-- keymap +vim.keymap.set("n", "<leader>cd", vim.cmd.Ex) +vim.keymap.set("n", "<leader>|", vim.cmd.vsplit) +vim.keymap.set("n", "<leader>_", vim.cmd.split) diff --git a/common/nvim.nix b/common/nvim.nix new file mode 100644 index 0000000..94403a9 --- /dev/null +++ b/common/nvim.nix @@ -0,0 +1,14 @@ +{ + programs.neovim = { + enable = true; + defaultEditor = true; + viAlias = true; + vimAlias = true; + + configure = { + customRC = '' + luafile ${./nvim.lua} + ''; + }; + }; +} diff --git a/common/options.nix b/common/options.nix new file mode 100644 index 0000000..e193dd1 --- /dev/null +++ b/common/options.nix @@ -0,0 +1,78 @@ +{ + lib, + config, + ... +}: let + cfg = config.customOps; +in { + options = with lib; { + customOps = mkOption { + description = "custom options"; + type = types.submodule { + options = { + owner = mkOption { + type = types.submodule { + options = { + username = mkOption { + type = types.str; + default = null; + }; + pubkey = mkOption { + type = types.str; + default = null; + }; + }; + }; + description = "machine owner"; + }; + domain = mkOption { + type = types.submodule { + options = { + name = mkOption { + type = types.str; + default = null; + }; + tld = mkOption { + type = types.str; + default = null; + }; + fqdn = mkOption { + type = types.str; + default = "${cfg.domain.name}.${cfg.domain.tld}"; + }; + }; + }; + description = "machine domain name"; + }; + mailAccounts = mkOption { + type = types.attrsOf (types.submodule { + options = { + passwdFile = mkOption { + type = types.str; + default = null; + }; + aliases = mkOption { + type = types.listOf types.str; + default = []; + }; + aliasesRegex = mkOption { + type = types.listOf types.str; + default = []; + }; + catchAll = mkOption { + type = types.listOf types.str; + default = []; + }; + sendOnly = mkOption { + type = types.bool; + default = false; + }; + }; + }); + description = "accounts for the mail server"; + }; + }; + }; + }; + }; +} diff --git a/common/ssh.nix b/common/ssh.nix new file mode 100644 index 0000000..3af78d7 --- /dev/null +++ b/common/ssh.nix @@ -0,0 +1,19 @@ +{config, ...}: { + users.users.root.openssh.authorizedKeys.keys = [ + config.customOps.owner.pubkey + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAII5gY2Jgg7MInzaWWq8c4+fT5DKdCBKM3kvgtqfcDxVI adonis" + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBcnEzwChlKUFUYHEUOQsCfVmkqm/FvWeItw3B0Z/uO4 aphrodite" + ]; + services.openssh = { + enable = true; + ports = [22]; + settings = { + PasswordAuthentication = false; + KbdInteractiveAuthentication = false; + AllowUsers = null; + UseDns = true; + X11Forwarding = false; + PermitRootLogin = "prohibit-password"; + }; + }; +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..dbcaeb0 --- /dev/null +++ b/flake.nix @@ -0,0 +1,56 @@ +{ + description = "toufy's server config"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11"; + sops-nix = { + url = "github:Mic92/sops-nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + simple-nixos-mailserver.url = "gitlab:simple-nixos-mailserver/nixos-mailserver/nixos-25.11"; + disko = { + url = "github:nix-community/disko"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = inputs @ { + self, + nixpkgs, + sops-nix, + simple-nixos-mailserver, + disko, + ... + }: { + nixosConfigurations = let + commonModules = [ + ./common + ./secrets.nix + disko.nixosModules.disko + sops-nix.nixosModules.sops + ]; + system = "x86_64-linux"; + specialArgs = {inherit inputs;}; + in { + adonis = nixpkgs.lib.nixosSystem { + inherit system; + inherit specialArgs; + modules = + [ + ./adonis + ] + ++ commonModules; + }; + aphrodite = nixpkgs.lib.nixosSystem { + inherit system; + inherit specialArgs; + modules = + [ + ./aphrodite + simple-nixos-mailserver.nixosModule + ] + ++ commonModules; + }; + }; + }; +} diff --git a/secrets.nix b/secrets.nix new file mode 100644 index 0000000..8d06a77 --- /dev/null +++ b/secrets.nix @@ -0,0 +1,11 @@ +{ + sops = { + defaultSopsFile = ./secrets.yaml; + defaultSopsFormat = "yaml"; + age = { + sshKeyPaths = ["/root/.ssh/id_ed25519"]; + keyFile = "/root/.config/sops/age/keys.txt"; + generateKey = true; + }; + }; +} diff --git a/secrets.yaml b/secrets.yaml new file mode 100644 index 0000000..596ef00 --- /dev/null +++ b/secrets.yaml @@ -0,0 +1,41 @@ +mailserver: + root: ENC[AES256_GCM,data:Csr3k2Vku2uJSeUidtGNmA5sfntg4GN0JicFG5xF3dIlx89QD1zjf7pugUoae/z7LSM66jWhL0ggXoPv,iv:J/ROA+fbh02S2wYdD/lMYbNVxTMqTVMAOm0YDq8vDKo=,tag:5DZzMWAJXq+kBGLSPeDKtQ==,type:str] + contact: ENC[AES256_GCM,data:VjQfXiEzvBrIeLwLtS2UPjG/fAICk3hUtFPRKHN+v7cd8aSc45u90Ho3uKyKvnIaVyfoRwN21NvK4Vbb,iv:VJbxNwzipmV2yIruBsHX4z/FNy+AJq8Xp97bw/Bogpc=,tag:bc3BwY4xQNmQbQZpIEynYQ==,type:str] + domain: ENC[AES256_GCM,data:tRt3kO+GCJ8HXrIaCVRKGnysZmafhi3znWohx4dqJB2xvEuaavzWYDCGPF4uGdoixpdQmKBe5Ri0hS3o,iv:HX9gOsStp9XDp5kR7mfeZrpU+Bt5Ajh/LtqiKf6lYLw=,tag:mW36a20S958iOY3EFG5IEg==,type:str] +searx: ENC[AES256_GCM,data:n451XLvOi2D2YvL0/+ko+HyXWEU7uuVlivkFsKxIzq1EWqMVEhFgEAt1k8W15AdgLY1xo455fUbL6/W1uSFO8w==,iv:QfX7s4l4QuZ8/85Q/+0OWezDGqOKXdY7B5M6wq/5tAM=,tag:ppZXewIAN0IdRMgrIIKTmg==,type:str] +concourse: + concourse_env: ENC[AES256_GCM,data:tS1yMXgOiHWlrXl6fOJttwQEuAsWAP3PYRzA86XkSgkOYmEu36qj8AEveX9+ZijkYBxFMAd1poZMZvJ/YqVL/tg61PxQgjjGIcOTulv+wQq+eAab8ssLMC6L3N5MNMeIzqm+D8t//ihYJvIE3YqJ6tqEhyuK8SSkVeHC9shWq8PebLL1c0bxv6Yjcv21YXYk5hbODfFIwpevtOfRzxSYSzToKIqOU/9oPadFe5Nufhcp8BU4hrSirA1nSoCVlVj+yn+1dtK3ApGTHPt0Y4ycC5Y6xjtCeFCdUjZRG64mRLrqYJwg9OQEAzVtdCl333EgJx8cRYUj66yoNxFq75J0iGDWcG1mjYiLt+BMtETPcbPrlIos/1EqUB2d4fjSlVfvjsk89+Ivj0Fg1fWTF97JClKTnZ1VUfMibJKaeUL9VP3sulTN1rVUMNKjfMOHs3XtaQUcHuS8RXcxa5U+gtZNmjLYct/l+sTaJIs2kSGNIJaLLSfN54UXLc3cBhYbhWd1owNakV7NSO2Ol5tuNm90Q4LNpJhY,iv:Wfb+R0Si1guKNbuR54V/gkbfpBaeBIgA+iU227uSCRI=,tag:O6FrToAguJkZdE3dZFM9Gw==,type:str] + postgres_env: ENC[AES256_GCM,data:auNph8cgw2xyC/dYDIK/I9lda8eX0PRvmLNWYcRC8v88iLKC9c/J/SiimDOE5T9Ok3o2c3XRu2Gq0YWKA8WctAUT37o12UpGsICeElLUmYQNi0m2tfW60e5Q5FknFjZITNdII8DY6pXgmsb0D3Oi5ZPjYrAuT3xUtarUtPn4LWGrX0mPKxqWqHwbHQ8Vplg+jRPjs2RHw4n3OX12U8KI,iv:w+IpmMITjIjvOTmBK9WmAIw5krWer82adPoSdVXhK8w=,tag:ioJ6AH9KgFV2Hxjp8U2pfQ==,type:str] +sops: + age: + - recipient: age1jcl6pr27ne5qmnadh723lhlu0js5dnt050akvaxmhvapm3yz9yqqkpakxs + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBnOHdGeERFalgrdGVwR3A0 + U3czbGVaRENpT1l1akI1dDFZeEVINk5VdjI4Cm5TNS9FYmlWWTFDOG5sZS9qVTF0 + YkRwN3lHYUpCWnBUbjFQaUNoRzd2Z00KLS0tIG5Yd01LMzJVSzY1cHN3VEl5U1k0 + dGJNUU13Y3BLS1grN0NrMkY5WnFXbHMKabbIFtexX7g1Cf7Y/PeFYwqRHQiEs/1b + NZeiARBQPmstHA0MAt5wMeax6zasVrDX+BKejHUUULyxKSuLOPs6yw== + -----END AGE ENCRYPTED FILE----- + - recipient: age1ul48hz3d4n4xr6fpux8w20effwtw4533gczef74cz4dsryd9zuzskdwj0e + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3YnFDMjBsdFo2UWVMeEpD + N3B1dWJhcmFUV245RVArTERMTnpnZ1ZQL0ZBCnY4YVNZVmQzL0tHOWQ0aVVrOWpx + amYyKzlyeXFEL1F4SE1UdE9INDdpb3cKLS0tIFJRdnZ1QVUzc0drLzdIa1QwOXV2 + Q25ZUlFneW05M3ZvVUpEbzFMMGZYVncKCEhqR07OsZAvDlVpR7drTR6bHo+4SzcA + LuoQfDN5k5qCDkKGUc+Nd061P/B7mTn3uzl4qKirEw1k3meN8zG7qg== + -----END AGE ENCRYPTED FILE----- + - recipient: age1ra6vn99y233pxrlpcwpuwl7vc8ma5y4ucg5jlkfylh6kudsxzehqznqmet + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBwdERBT1dQbTE3Ulg5QTBu + K2RPYWdHQkw2cEtPNlJERGlCeThTQzdCS1VjCmJMUHY5aTdveWl5UzM5ZW9SNmkx + OG1zSkhSQnVlZDJpZmZCaWhLSDVjdWMKLS0tIEQzRTZyRGlyKzJVU2JBYklxUmx1 + eExvNEFWWkM1Tlp1azVqbWlHTlE2VUEKQX3nhH1R3l1Zx2Fs8Jj3P188aa0j+oba + AjeKUiRMROq4PMJYmWXDjToBlvdOFq9oYNl+1mNlcQTMbEkCoqIfrw== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2026-04-17T11:12:23Z" + mac: ENC[AES256_GCM,data:R5i+GcK5cCo0oYBN2CC9IPrRMTfsFFhnBa8/9J60KZwBXRRrM+2B/8Scwjd+9nqC27WEIZ/B42J9/JFr2g0h+g6AmhO0Vss5KiSebb6KsGxuYdnGf/YglMNMrNpGPWEQ7qZOU+DuAWObkavbL07Ce6ltYik3fbCErT+HTHuKoAM=,iv:vZWyOwHO+NPRHQA+PHAm6kROqHTymBQWc8LQ1RdvRvM=,tag:0auZORTvh3HFKkZGspNH+g==,type:str] + unencrypted_suffix: _unencrypted + version: 3.12.2 |
