summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortoufic ar <contact@toufy.me>2026-04-22 14:08:19 +0300
committertoufic ar <contact@toufy.me>2026-04-22 14:08:19 +0300
commitc7349e466fef7ecff5a46b1d0c819975a6bdcb8c (patch)
tree2a3fc53016ae4d0b31d7583171bd4b8e60f4cc17
downloadservers-c7349e466fef7ecff5a46b1d0c819975a6bdcb8c.tar.gz
servers-c7349e466fef7ecff5a46b1d0c819975a6bdcb8c.zip
initial commit
-rw-r--r--.sops.yaml11
-rw-r--r--adonis/aur/default.nix10
-rw-r--r--adonis/captiveportal/default.nix14
-rw-r--r--adonis/default.nix9
-rw-r--r--adonis/network.nix20
-rw-r--r--adonis/tor/default.nix45
-rw-r--r--adonis/tor/snowflake.nix19
-rw-r--r--aphrodite/default.nix9
-rw-r--r--aphrodite/devops/default.nix6
-rw-r--r--aphrodite/devops/docker.nix13
-rw-r--r--aphrodite/devops/git.nix95
-rw-r--r--aphrodite/mail/default.nix79
-rw-r--r--aphrodite/network.nix20
-rw-r--r--aphrodite/search/default.nix108
-rw-r--r--aphrodite/search/engines.nix166
-rw-r--r--common/default.nix24
-rw-r--r--common/disks.nix60
-rw-r--r--common/fail2ban.nix13
-rw-r--r--common/hardware.nix16
-rw-r--r--common/mail.nix37
-rw-r--r--common/network.nix19
-rw-r--r--common/nginx.nix55
-rw-r--r--common/nvim.lua27
-rw-r--r--common/nvim.nix14
-rw-r--r--common/options.nix78
-rw-r--r--common/ssh.nix19
-rw-r--r--flake.nix56
-rw-r--r--secrets.nix11
-rw-r--r--secrets.yaml41
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