󰣻 flake.nix — infrastructure overview
# ymrtech/nix-config
# Centralized NixOS configuration for my entire infrastructure
󰣨 tree
├── hosts/
│ ├── command/ # Bare-metal host (my own hardware)
│ ├── giga/ # Workstation (Gigabyte laptop + eGPU)
│ ├── mail/ # Mail + monitoring hub (Oracle Free Tier)
│ ├── public/ # Public services (Oracle Free Tier)
│ └── vpn/ # VPN gateway + fleet operator (Oracle Free Tier)
├── hosts/common/ # Shared modules
│ ├── global/ # Shared host settings (hardening, zram, DNS zone, health gate)
│ ├── optional/ # Optional services
│ └── users/ # User configs
├── overlays/ # Custom package overrides
├── modules/ # Custom NixOS modules
├── flake.nix # Main flake entry point
└── flake.lock # Dependency lock file

Architecture Philosophy

My infrastructure follows a “think zen garden, not herd” philosophy. Rather than managing dozens of independent servers with disparate configs, I use NixOS Flakes and Modules to maintain a single source of truth for every system.

Core Principles

  1. Declarative — Every system’s state is defined in code. No manual changes on running servers.
  2. Reproducible — Copy a system’s config, rebuild, and get an identical system.
  3. Modular — Common settings live in shared modules; system-specific settings override as needed.
  4. Version-controlled — The entire OS lives in Git. Changes are reviewed, committed, and deployed.
  5. Atomic — New configurations are evaluated before activation. Rollback is instant.
  6. Health-gated — Every deploy settles, checks its critical services, and rolls itself back if one is down.

How a change reaches a host

  1. Change lands as a pull request; CI evaluates and builds every affected host.
  2. On merge to main, a self-hosted runner deploys the affected hosts.
  3. The new generation activates, then a 40-second settle window runs.
  4. Critical services are checked. Any violation rolls the host back automatically.
  5. A weekly flake update job moves inputs forward — it is the only unattended path that changes them.

There are no automatic unattended upgrades of the running system: hosts move when main moves.

Host Breakdown

command — Bare-Metal Host

My own hardware, joined to the mesh at 11.0.0.3.

giga — Workstation

My daily driver — a Gigabyte laptop with an RTX 3090 in an eGPU enclosure.

vpn — VPN Gateway (Oracle Free Tier)

The backbone of my network, at 11.0.0.1. Routes all traffic through WireGuard to a West Montreal datacenter. It also runs the Hermes agent that operates this fleet.

Two client tiers share the same gateway with different DNS postures:

Interface Gateway DNS posture
wg0 11.0.0.1 fleet mesh — vpn, command, giga, mail, public
wg1 172.16.40.1 privacy — Unbound, DNS-over-TLS to Quad9 + Cloudflare
wg2 172.16.41.1 security — AdGuard Home filtering
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# hosts/vpn/default.nix
{
  networking.hostName = "vpn";
  networking.nat.enable = true;
  networking.nat.externalInterface = "eth0";

  wg-quick.interfaces.wg0 = {
    type = "amneziaWG";
    listenPort = 41020;
    peers = [
      { # command (bare-metal)
        publicKey = "...";
        allowedIPs = [ "11.0.0.3/32" ];
      }
      # giga .5, mail .6, public .7 — the rest of the mesh is defined
      # in the private flake, alongside the wg1/wg2 client tiers.
    ];
  };

  services.unbound = {
    enable = true;
    settings = {
      # Forward all DNS through Quad9 and Cloudflare over TLS
      forward-zone = [{
        name = ".";
        forward-tls-upstream = "yes";
        forward-addr = [
          "149.112.112.112@853#dns.quad9.net"
          "1.0.0.1@853#one.one.one.one"
        ];
      }];
    };
  };
}

mail — Mail & Monitoring Hub (Oracle Free Tier)

Self-hosted mail server with full email stack, and the host that runs the central observability stack.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# hosts/mail/default.nix
{
  networking.hostName = "mail";

  mailserver = {
    enable = true;
    fqdn = "mail.ymrtech.com";
    domains = [ "ymrtech.com" "trvtk.net" ];
    dmarcReporting.enable = true;
    fullTextSearch.enable = true;

    # Mailboxes and aliases (yannick@, hermes@, notify@, …) are declared
    # here; every password comes from SOPS, never from the repo.
    accounts = {
      "yannick@ymrtech.com".passwordFile =
        config.sops.secrets.mail-password.path;
      "hermes@ymrtech.com".passwordFile =
        config.sops.secrets.hermes-mail-password.path;
    };
  };
}

public — Public Services (Oracle Free Tier)

The only internet-facing host in the fleet. Everything else is reachable solely over the mesh. Firewall opens just 80/443 TCP (web + ACME), 3478/5349 TCP/UDP (TURN), and the corresponding UDP port range.

Shared Infrastructure

Common Modules (hosts/common/)

All hosts inherit these shared configurations:

Monitoring Stack

Component Purpose
VictoriaMetrics Metrics storage and query engine
VMAgent Metrics collection and forwarding
VictoriaLogs Log storage, with a 14-day retention window
vlagent Log shipping from every host
Grafana Dashboards and alerting rules
Uptime Kuma External endpoint monitoring
autokuma Publishes monitor state to the public status page

Security Features

󰔟 Want this level of infrastructure for your team?
I can design, build, and manage your NixOS infrastructure — from a single server to a multi-host fleet. Declarative configs, atomic deployments, instant rollbacks.
[ REQUEST AUDIT ]
󰣨 ymrtech@ymrtech | 󰌠 NixOS | 󰍢 UTF-8