diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..947f765 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +configuration.nix +hardware-configuration.nix +*~ diff --git a/config/fudo/acme-for-hostname.nix b/config/fudo/acme-for-hostname.nix index 4dd5f2e..0451170 100644 --- a/config/fudo/acme-for-hostname.nix +++ b/config/fudo/acme-for-hostname.nix @@ -6,26 +6,28 @@ with lib; let cfg = config.fudo.acme; - wwwRoot = hostname: - pkgs.writeTextFile { - name = "index.html"; + # wwwRoot = hostname: + # pkgs.writeTextFile { + # name = "index.html"; - text = '' - - - ${hostname} - - -

${hostname} - - - ''; - destination = "/www"; - }; + # text = '' + # + # + # ${hostname} + # + # + #

${hostname} + # + # + # ''; + # destination = "/www"; + # }; in { options.fudo.acme = { + enable = mkEnableOption "Fetch ACME certs for supplied local hostnames."; + hostnames = mkOption { type = with types; listOf str; description = "A list of hostnames mapping to this host, for which to acquire SSL certificates."; @@ -35,9 +37,15 @@ in { "alt.hostname.com" ]; }; + + admin-address = mkOption { + type = types.str; + description = "The admin address in charge of these addresses."; + default = "admin@fudo.org"; + }; }; - config = { + config = mkIf cfg.enable { services.nginx = { enable = true; @@ -49,13 +57,13 @@ in { { enableACME = true; forceSSL = true; - root = (wwwRoot hostname) + ("/" + "www"); + # root = (wwwRoot hostname) + ("/" + "www"); }) cfg.hostnames); }; security.acme.certs = listToAttrs - (map (hostname: nameValuePair hostname { email = "admin@fudo.org"; }) + (map (hostname: nameValuePair hostname { email = cfg.admin-address; }) cfg.hostnames); }; } diff --git a/config/fudo/chat.nix b/config/fudo/chat.nix index dfaf28c..ecc03ed 100644 --- a/config/fudo/chat.nix +++ b/config/fudo/chat.nix @@ -32,7 +32,7 @@ in { }; smtp-password-file = mkOption { - type = types.path; + type = types.str; description = "Path to a file containing the password to use while connecting to the SMTP server."; }; @@ -61,7 +61,7 @@ in { }; password-file = mkOption { - type = types.path; + type = types.str; description = "Path to file containing database password."; }; }; diff --git a/config/fudo/common.nix b/config/fudo/common.nix index 93c9823..78f3fd8 100644 --- a/config/fudo/common.nix +++ b/config/fudo/common.nix @@ -16,7 +16,7 @@ with lib; domain = mkOption { type = types.str; description = '' - Domain of the + Domain of the local network. ''; }; @@ -52,5 +52,7 @@ with lib; description = "Email for administrator of this system."; default = "admin@fudo.org"; }; + + enable-gui = mkEnableOption "Install desktop GUI software."; }; } diff --git a/config/fudo/dns.nix b/config/fudo/dns.nix new file mode 100644 index 0000000..9de4805 --- /dev/null +++ b/config/fudo/dns.nix @@ -0,0 +1,218 @@ +{ lib, config, pkgs, ... }: + +with lib; + +let + cfg = config.fudo.dns; + + ip = import ../../lib/ip.nix { lib = lib; }; + + join-lines = concatStringsSep "\n"; + + hostOpts = { host, ...}: { + options = { + ip-addresses = mkOption { + type = with types; listOf str; + description = '' + A list of IPv4 addresses assigned to this host. + ''; + default = []; + }; + + ipv6-addresses = mkOption { + type = with types; listOf str; + description = '' + A list of IPv6 addresses assigned to this host. + ''; + default = []; + }; + + ssh-fingerprints = mkOption { + type = with types; listOf str; + description = '' + A list of DNS SSHFP records for this host. + ''; + }; + }; + }; + + srvRecordOpts = with types; { + options = { + weight = mkOption { + type = int; + description = "Weight relative to other records."; + default = 1; + }; + + priority = mkOption { + type = int; + description = "Priority to give this record."; + default = 0; + }; + + port = mkOption { + type = port; + description = "Port to use while connecting to this service."; + }; + + host = mkOption { + type = str; + description = "Host that provides this service."; + example = "my-host.my-domain.com"; + }; + }; + }; + + domainOpts = { domain, ... }: with types; { + options = { + hosts = mkOption { + type = loaOf (submodule hostOpts); + default = {}; + description = "A map of hostname to { host_attributes }."; + }; + + dnssec = mkEnableOption "Enable DNSSEC security for this zone."; + + mx = mkOption { + type = listOf str; + description = "A list of mail servers serving this domain."; + default = []; + }; + + srv-records = mkOption { + type = attrsOf (attrsOf (listOf (submodule srvRecordOpts))); + description = "Map of traffic type to srv records."; + default = {}; + example = { + tcp = { + kerberos = { + port = 88; + host = "auth-host.my-domain.com"; + }; + }; + }; + }; + + aliases = mkOption { + type = loaOf str; + default = {}; + description = "A mapping of host-alias => hostnames to add to DNS."; + example = { + "music" = "host.dom.com."; + "mail" = "hostname"; + }; + }; + + extra-dns-records = mkOption { + type = listOf str; + description = "Records to be inserted verbatim into the DNS zone."; + example = ["some-host IN CNAME base-host"]; + default = []; + }; + + dmarc-report-address = mkOption { + type = nullOr str; + description = "The email to use to recieve DMARC reports, if any."; + example = "admin-user@domain.com"; + default = null; + }; + + default-host = mkOption { + type = nullOr str; + description = "IP of the host which will act as the default server for this domain, if any."; + default = null; + }; + }; + }; + + hostARecords = host: data: + join-lines ((map (ip: "${host} IN A ${ip}") data.ip-addresses) ++ + (map (ip: "${host} IN AAAA ${ip}") data.ipv6-addresses)); + + makeSrvRecords = protocol: type: records: + join-lines (map (record: "_${type}._${protocol} IN SRV ${toString record.priority} ${toString record.weight} ${toString record.port} ${toString record.host}.") + records); + + makeSrvProtocolRecords = protocol: types: join-lines (mapAttrsToList (makeSrvRecords protocol) types); + + cnameRecord = alias: host: "${alias} IN CNAME ${host}"; + + hostSshFpRecords = host: data: join-lines (map (sshfp: "${host} IN SSHFP ${sshfp}") data.ssh-fingerprints); + + mxRecords = mxs: + concatStringsSep "\n" + (map (mx: "@ IN MX 10 ${mx}.") mxs); + + dmarcRecord = dmarc-email: + optionalString (dmarc-email != null) + ''_dmarc IN TXT "v=DMARC1;p=quarantine;sp=quarantine;rua=mailto:${dmarc-email};"''; + + nsRecords = ns-hosts: + join-lines ((mapAttrsToList (host: _: "@ IN NS ${host}.") ns-hosts) ++ + (mapAttrsToList (host: ip: "${host} IN A ${ip}") ns-hosts)); + +in { + + options.fudo.dns = with types; { + enable = mkEnableOption "Enable master DNS services."; + + # FIXME: This should allow for AAAA addresses too... + dns-hosts = mkOption { + type = loaOf str; + description = "Map of domain nameserver FQDNs to IP."; + example = { "ns1.domain.com" = "1.1.1.1"; }; + }; + + domains = mkOption { + type = loaOf (submodule domainOpts); + default = {}; + description = "A map of domain to domain options."; + }; + + listen-ips = mkOption { + type = listOf str; + description = "A list of IPs on which to listen for DNS queries."; + example = ["1.2.3.4"]; + }; + }; + + config = mkIf cfg.enable { + services.nsd = { + enable = true; + identity = "procul.informis.land"; + interfaces = cfg.listen-ips; + zones = mapAttrs' (dom: dom-cfg: + nameValuePair "${dom}." { + dnssec = dom-cfg.dnssec; + + data = '' + $ORIGIN ${dom}. + $TTL 12h + + @ IN SOA ns1.${dom}. hostmaster.${dom}. ( + ${toString builtins.currentTime} + 5m + 2m + 6w + 5m) + + ${optionalString (dom-cfg.default-host != null) "@ IN A ${dom-cfg.default-host}"} + + ${mxRecords dom-cfg.mx} + + $TTL 6h + + ${nsRecords cfg.dns-hosts} + + ${dmarcRecord dom-cfg.dmarc-report-address} + + ${join-lines (mapAttrsToList makeSrvProtocolRecords dom-cfg.srv-records)} + ${join-lines (mapAttrsToList hostARecords dom-cfg.hosts)} + ${join-lines (mapAttrsToList hostSshFpRecords dom-cfg.hosts)} + ${join-lines (mapAttrsToList cnameRecord dom-cfg.aliases)} + ${join-lines dom-cfg.extra-dns-records} + ''; + }) cfg.domains; + }; + }; +} diff --git a/config/fudo/local-network.nix b/config/fudo/local-network.nix index 29967c2..4cc653a 100644 --- a/config/fudo/local-network.nix +++ b/config/fudo/local-network.nix @@ -1,96 +1,276 @@ -# UNFINISHED! -# -# The plan is to bootstrap a local network config: DNS, DHCP, etc. - { lib, config, pkgs, ... }: with lib; let - hostOpts = { config, ... }: { - options = { - ipv6Address = mkOption { - type = types.str; - description = '' - The V6 IP of a given host, if any. - ''; - }; + cfg = config.fudo.local-network; - ipv4Address = mkOption { + join-lines = concatStringsSep "\n"; + + ip = import ../../lib/ip.nix { lib = lib; }; + + hostOpts = { hostname, ... }: { + options = { + ip-address = mkOption { type = types.str; description = '' The V4 IP of a given host, if any. ''; }; - macAddress = mkOption { + mac-address = mkOption { type = types.str; description = '' The MAC address of a given host, if desired for IP reservation. ''; }; + + ssh-fingerprints = mkOption { + type = with types; listOf str; + description = "A list of DNS SSHFP records for this host."; + default = []; + }; }; }; - localNameServerOpts = { config, ... }: { + traceout = out: builtins.trace out out; + + srvRecordOpts = with types; { options = { - ipv6Address = mkOption { - type = types.str; - description = '' - The V6 IP of this nameserver, if any. - ''; + weight = mkOption { + type = int; + description = "Weight relative to other records."; + default = 1; }; - ipv4Address = mkOption { - type = types.str; - description = '' - The V4 IP of this nameserver, if any. - ''; + priority = mkOption { + type = int; + description = "Priority to give this record."; + default = 0; }; - ipv4ReverseDomain = mkOption { - type = types.str; - description = '' - The domain of the IPv4 address range for which this nameserver is responsible. + port = mkOption { + type = port; + description = "Port to use when connecting."; + }; - Eg: 0.10.in-addr.arpa - ''; + host = mkOption { + type = str; + description = "Host to contact for this service."; + example = "my-host.my-domain.com."; }; }; }; in { - options = { + options.fudo.local-network = { - fudo.localNetwork.hosts = mkOption { - type = types.listOf (submodule hostOpts); + enable = mkEnableOption "Enable local network configuration (DHCP & DNS)."; + + hosts = mkOption { + type = with types; loaOf (submodule hostOpts); default = {}; - description = '' - A map of hostname => { host_attributes }. - ''; + description = "A map of hostname => { host_attributes }."; }; - fudo.localNetwork.domain = mkOption { + domain = mkOption { + type = types.str; + description = "The domain to use for the local network."; + }; + + dns-servers = mkOption { + type = with types; listOf str; + description = "A list of domain name server to use for the local network."; + }; + + dhcp-interfaces = mkOption { + type = with types; listOf str; + description = "A list of interfaces on which to serve DHCP."; + }; + + dns-serve-ips = mkOption { + type = with types; listOf str; + description = "A list of IPs on which to server DNS queries."; + }; + + gateway = mkOption { + type = types.str; + description = "The gateway to use for the local network."; + }; + + aliases = mkOption { + type = with types; loaOf str; + default = {}; + description = "A mapping of host-alias => hostname to use on the local network."; + }; + + network = mkOption { + type = types.str; + description = "Network to treat as local."; + }; + + enable-reverse-mappings = mkOption { + type = types.bool; + description = "Genereate PTR reverse lookup records."; + default = false; + }; + + dhcp-dynamic-network = mkOption { type = types.str; description = '' - The domain to use for the local network. + The network from which to dynamically allocate IPs via DHCP. + + Must be a subnet of . ''; }; - fudo.localNetwork.hostAliases = mkOption { - type = types.attrsOf types.str; + recursive-resolver = mkOption { + type = types.str; + description = "DNS nameserver to use for recursive resolution."; + }; + + server-ip = mkOption { + type = types.str; + description = "IP of the DNS server."; + }; + + extra-dns-records = mkOption { + type = with types; listOf str; + description = "Records to be inserted verbatim into the DNS zone."; + example = ["some-host IN CNAME other-host"]; + default = []; + }; + + srv-records = mkOption { + type = with types; attrsOf (attrsOf (listOf (submodule srvRecordOpts))); + description = "Map of traffic type to srv records."; default = {}; - description = '' - A mapping of hostAlias => hostName to use on the local network. + example = { + tcp = { + kerberos = { + port = 88; + host = "auth-host.my-domain.com"; + }; + }; + }; + }; + + search-domains = mkOption { + type = with types; listOf str; + description = "A list of domains to search for DNS names."; + example = ["my-domain.com" "other-domain.com"]; + default = []; + }; + + # TODO: srv records + }; + + config = mkIf cfg.enable { + services.dhcpd4 = { + enable = true; + + machines = mapAttrsToList (hostname: hostOpts: { + ethernetAddress = hostOpts.mac-address; + hostName = hostname; + ipAddress = hostOpts.ip-address; + }) cfg.hosts; + + interfaces = cfg.dhcp-interfaces; + + extraConfig = '' + subnet ${ip.getNetworkBase cfg.network} netmask ${ip.maskFromV32Network cfg.network} { + authoritative; + option subnet-mask ${ip.maskFromV32Network cfg.network}; + option broadcast-address ${ip.networkMaxIp cfg.network}; + option routers ${cfg.gateway}; + option domain-name-servers ${concatStringsSep " " cfg.dns-servers}; + option domain-name "${cfg.domain}"; + option domain-search ${join-lines (map (dom: "\"${dom}\"") ([cfg.domain] ++ cfg.search-domains))}; + range ${ip.networkMinIp cfg.dhcp-dynamic-network} ${ip.networkMaxButOneIp cfg.dhcp-dynamic-network}; + } ''; }; - fudo.localNetwork.localNameServer = mkOption { - type = (submodule localNameServerOpts); - description = '' - The master nameserver of the local network. - ''; + services.bind = let + blockHostsToZone = block: hosts-data: { + master = true; + name = "${block}.in-addr.arpa"; + file = let + # We should add these...but need a domain to assign them to. + # ip-last-el = ip: toInt (last (splitString "." ip)); + # used-els = map (host-data: ip-last-el host-data.ip-address) hosts-data; + # unused-els = subtractLists used-els (map toString (range 1 255)); + + in pkgs.writeText "db.${block}-zone" '' + $ORIGIN ${block}.in-addr.arpa. + $TTL 1h + + @ IN SOA ns1.${cfg.domain}. hostmaster.${cfg.domain}. ( + ${toString builtins.currentTime} + 1800 + 900 + 604800 + 1800) + + @ IN NS ns1.${cfg.domain}. + + ${join-lines (map hostPtrRecord hosts-data)} + ''; + }; + + ipToBlock = ip: concatStringsSep "." (reverseList (take 3 (splitString "." ip))); + compactHosts = mapAttrsToList (host: data: data // { host = host; }) cfg.hosts; + hostsByBlock = groupBy (host-data: ipToBlock host-data.ip-address) compactHosts; + hostPtrRecord = host-data: + "${last (splitString "." host-data.ip-address)} IN PTR ${host-data.host}.${cfg.domain}."; + + blockZones = mapAttrsToList blockHostsToZone hostsByBlock; + + hostARecord = host: data: "${host} IN A ${data.ip-address}"; + hostSshFpRecords = host: data: join-lines (map (sshfp: "${host} IN SSHFP ${sshfp}") data.ssh-fingerprints); + cnameRecord = alias: host: "${alias} IN CNAME ${host}"; + + makeSrvRecords = protocol: type: records: + join-lines (map (record: "_${type}._${protocol} IN SRV ${toString record.priority} ${toString record.weight} ${toString record.port} ${record.host}.") + records); + + makeSrvProtocolRecords = protocol: types: join-lines (mapAttrsToList (makeSrvRecords protocol) types); + + in { + enable = true; + cacheNetworks = [ cfg.network "localhost" "localnets" ]; + forwarders = [ cfg.recursive-resolver ]; + listenOn = cfg.dns-serve-ips; + zones = [ + { + master = true; + name = cfg.domain; + file = pkgs.writeText "${cfg.domain}-zone" '' + @ IN SOA ns1.${cfg.domain}. hostmaster.${cfg.domain}. ( + ${toString builtins.currentTime} + 5m + 2m + 6w + 5m) + + $TTL 1h + + @ IN NS ns1.${cfg.domain}. + + $ORIGIN ${cfg.domain}. + + $TTL 30m + + ns1 IN A ${cfg.server-ip} + ${join-lines (mapAttrsToList hostARecord cfg.hosts)} + ${join-lines (mapAttrsToList hostSshFpRecords cfg.hosts)} + ${join-lines (mapAttrsToList cnameRecord cfg.aliases)} + ${join-lines cfg.extra-dns-records} + ${join-lines (mapAttrsToList makeSrvProtocolRecords cfg.srv-records)} + ''; + } + ] ++ blockZones; }; }; } diff --git a/config/fudo/mail-container.nix b/config/fudo/mail-container.nix index aa7edb5..e12b9ff 100644 --- a/config/fudo/mail-container.nix +++ b/config/fudo/mail-container.nix @@ -160,21 +160,21 @@ in rec { }; }; - users = { - users = { - ${container-mail-user} = { - isSystemUser = true; - uid = container-mail-user-id; - group = "mailer"; - }; - }; + # users = { + # users = { + # ${container-mail-user} = { + # isSystemUser = true; + # uid = container-mail-user-id; + # group = "mailer"; + # }; + # }; - groups = { - ${container-mail-group} = { - members = ["mailer"]; - }; - }; - }; + # groups = { + # ${container-mail-group} = { + # members = ["mailer"]; + # }; + # }; + # }; fudo.mail-server = { enable = true; @@ -193,10 +193,12 @@ in rec { dovecot = { ssl-certificate = "/etc/${container-dovecot-cert}"; ssl-private-key = "/etc/dovecot-certs/key.pem"; - ldap-ca = "/etc/${container-fudo-ca-cert}"; - ldap-urls = cfg.dovecot.ldap-urls; - ldap-reader-dn = cfg.dovecot.ldap-reader-dn; - ldap-reader-passwd = cfg.dovecot.ldap-reader-passwd; + ldap = { + # ca = "/etc/${container-fudo-ca-cert}"; + server-urls = cfg.dovecot.ldap.server-urls; + reader-dn = cfg.dovecot.ldap.reader-dn; + reader-passwd = cfg.dovecot.ldap.reader-passwd; + }; }; local-domains = cfg.local-domains; diff --git a/config/fudo/mail.nix b/config/fudo/mail.nix index 7b664a8..ba350fd 100644 --- a/config/fudo/mail.nix +++ b/config/fudo/mail.nix @@ -104,6 +104,7 @@ in { user-aliases = mkOption { type = with types; loaOf(listOf str); description = "A map of real user to list of aliases."; + default = {}; example = { someuser = ["alias0" "alias1"]; }; @@ -167,4 +168,22 @@ in { ./mail/rspamd.nix ./mail/clamav.nix ]; + + config = mkIf cfg.enable { + users = { + users = { + mailuser = { + isSystemUser = true; + uid = cfg.mail-user-id; + group = "mailgroup"; + }; + }; + + groups = { + mailgroup = { + members = ["mailuser"]; + }; + }; + }; + }; } diff --git a/config/fudo/mail/dovecot.nix b/config/fudo/mail/dovecot.nix index 6c5d4fd..ae994b7 100644 --- a/config/fudo/mail/dovecot.nix +++ b/config/fudo/mail/dovecot.nix @@ -23,57 +23,85 @@ let ''; }; - ldap-conf = filename: uris: - pkgs.writeText filename '' - uris = ${concatStringsSep " " uris} + ldap-conf = filename: config: + let + ssl-config = if config.ca == null then '' + tls = no + tls_require_cert = try + '' else '' + tls_ca_cert_file = ${config.ca} + tls = yes + tls_require_cert = try + ''; + + in + pkgs.writeText filename '' + uris = ${concatStringsSep " " config.server-urls} ldap_version = 3 - dn = ${cfg.dovecot.ldap-reader-dn} - dnpass = ${cfg.dovecot.ldap-reader-passwd} + dn = ${config.reader-dn} + dnpass = ${config.reader-passwd} auth_bind = yes auth_bind_userdn = uid=%u,ou=members,dc=fudo,dc=org base = dc=fudo,dc=org - # tls_ca_cert_file = ${cfg.dovecot.ldap-ca} - # FIXME: turn back on when certs work - tls = no - tls_require_cert = try + ${ssl-config} ''; + ldap-passwd-entry = ldap-config: '' + passdb { + driver = ldap + args = ${ldap-conf "ldap-passdb.conf" ldap-config} + } + ''; + + ldapOpts = { + options = with types; { + ca = mkOption { + type = nullOr str; + description = "The path to the CA cert used to sign the LDAP server certificate."; + default = null; + }; + + server-urls = mkOption { + type = listOf str; + description = "A list of LDAP server URLs used for authentication."; + }; + + reader-dn = mkOption { + type = str; + description = '' + DN to use for reading user information. Needs access to homeDirectory, + uidNumber, gidNumber, and uid, but not password attributes. + ''; + }; + + reader-passwd = mkOption { + type = str; + description = '' + Password for the user specified in ldap-reader-dn. + ''; + }; + }; + }; + dovecot-user = config.services.dovecot2.user; in { - options.fudo.mail-server.dovecot = { + options.fudo.mail-server.dovecot = with types; { ssl-private-key = mkOption { - type = types.str; + type = str; description = "Location of the server SSL private key."; }; ssl-certificate = mkOption { - type = types.str; + type = str; description = "Location of the server SSL certificate."; }; - ldap-ca = mkOption { - type = types.str; - description = "The path to the CA cert used to sign the LDAP server certificate."; - }; - - ldap-urls = mkOption { - type = with types; listOf str; - description = "The urls of LDAP servers."; - }; - - ldap-reader-dn = mkOption { - type = types.str; + ldap = mkOption { + type = nullOr (submodule ldapOpts); + default = null; description = '' - DN to use for reading user information. Needs access to homeDirectory, - uidNumber, gidNumber, and uid, but not password attributes. - ''; - }; - - ldap-reader-passwd = mkOption { - type = types.str; - description = '' - Password for the user specified in ldap-reader-dn. + LDAP auth server configuration. If omitted, the server will use local authentication. ''; }; }; @@ -93,8 +121,7 @@ in { enableImap = true; enableLmtp = true; enablePop3 = true; - enablePAM = false; - + enablePAM = cfg.dovecot.ldap == null; createMailUser = true; @@ -124,14 +151,16 @@ in { extraConfig = '' #Extra Config - # The prometheus exporter still expects an older style of metrics - mail_plugins = $mail_plugins old_stats - service old-stats { - unix_listener old-stats { - user = dovecot-exporter - group = dovecot-exporter + ${optionalString cfg.monitoring '' + # The prometheus exporter still expects an older style of metrics + mail_plugins = $mail_plugins old_stats + service old-stats { + unix_listener old-stats { + user = dovecot-exporter + group = dovecot-exporter + } } - } + ''} ${lib.optionalString cfg.debug '' mail_debug = yes @@ -170,15 +199,15 @@ in { } # Drop privs, since all mail is owned by one user - user = ${cfg.mail-user} - group = ${cfg.mail-group} + # user = ${cfg.mail-user} + # group = ${cfg.mail-group} + user = root } auth_mechanisms = login plain - passdb { - driver = ldap - args = ${ldap-conf "ldap-passdb.conf" cfg.dovecot.ldap-urls} - } + + ${optionalString (cfg.dovecot.ldap != null) + (ldap-passwd-entry cfg.dovecot.ldap)} userdb { driver = static args = uid=${toString cfg.mail-user-id} home=${cfg.mail-directory}/%u @@ -189,8 +218,18 @@ in { unix_listener auth { mode = 0660 user = "${config.services.postfix.user}" - group = ${config.services.postfix.group} + group = ${cfg.mail-group} } + + unix_listener auth-userdb { + mode = 0660 + user = "${config.services.postfix.user}" + group = ${cfg.mail-group} + } + } + + service auth-worker { + user = root } namespace inbox { @@ -236,7 +275,8 @@ in { done chown -R '${dovecot-user}:${cfg.mail-group}' '${state-directory}/imap_sieve' - chown ${cfg.mail-user}:${cfg.mail-group} ${cfg.mail-directory} + chown '${cfg.mail-user}:${cfg.mail-group}' ${cfg.mail-directory} + chmod g+w ${cfg.mail-directory} ''; }; } diff --git a/config/fudo/mail/postfix.nix b/config/fudo/mail/postfix.nix index 776c584..d05bf6d 100644 --- a/config/fudo/mail/postfix.nix +++ b/config/fudo/mail/postfix.nix @@ -6,6 +6,14 @@ let cfg = config.fudo.mail-server; + # The final newline is important + write-entries = filename: entries: + let + entries-string = (concatStringsSep "\n" entries); + in builtins.toFile filename '' + ${entries-string} + ''; + make-user-aliases = entries: concatStringsSep "\n" (mapAttrsToList (user: aliases: @@ -39,25 +47,22 @@ let /^User-Agent:/ IGNORE /^X-Enigmail:/ IGNORE ''); - blacklist-postfix-entry = sender: "${sender} REJECT"; - blacklist-postfix-file = entries: - concatStringsSep "\n" (map blacklist-postfix-entry entries); - sender-blacklist-file = builtins.toFile "reject_senders" - (blacklist-postfix-file cfg.sender-blacklist); - recipient-blacklist-file = builtins.toFile "reject_recipients" - (blacklist-postfix-file cfg.recipient-blacklist); + blacklist-postfix-file = filename: entries: + write-entries filename entries; + sender-blacklist-file = blacklist-postfix-file "reject_senders" + (map blacklist-postfix-entry cfg.sender-blacklist); + recipient-blacklist-file = blacklist-postfix-file "reject_recipients" + (map blacklist-postfix-entry cfg.recipient-blacklist); # A list of domains for which we accept mail - virtual-mailbox-map-file = builtins.toFile "virtual_mailbox_map" - (concatStringsSep "\n" - (map (domain: "@${domain} OK") (cfg.local-domains ++ [cfg.domain]))); + virtual-mailbox-map-file = write-entries "virtual_mailbox_map" + (map (domain: "@${domain} OK") (cfg.local-domains ++ [cfg.domain])); sender-login-map-file = let escapeDot = (str: replaceStrings ["."] ["\\."] str); - in builtins.toFile "sender_login_maps" - (concatStringsSep "\n" - (map (domain: "/^(.*)@${escapeDot domain}$/ \${1}") (cfg.local-domains ++ [cfg.domain]))); + in write-entries "sender_login_maps" + (map (domain: "/^(.*)@${escapeDot domain}$/ \${1}") (cfg.local-domains ++ [cfg.domain])); mapped-file = name: "hash:/var/lib/postfix/conf/${name}"; @@ -106,9 +111,8 @@ in { origin = cfg.domain; hostname = cfg.hostname; destination = ["localhost" "localhost.localdomain"]; - # destination = ["localhost" "localhost.localdomain"] ++ - # (map (domain: "localhost.${domain}") cfg.local-domains) ++ - # cfg.local-domains; + # destination = ["localhost" "localhost.localdomain" cfg.hostname] ++ + # cfg.local-domains;; enableHeaderChecks = true; enableSmtp = true; @@ -133,7 +137,7 @@ in { sslKey = cfg.postfix.ssl-private-key; config = { - virtual_mailbox_domains = builtins.toFile "domain-list" (concatStringsSep "\n" cfg.local-domains); + virtual_mailbox_domains = cfg.local-domains ++ [cfg.domain]; # virtual_mailbox_base = "${cfg.mail-directory}/"; virtual_mailbox_maps = mapped-file "virtual_mailbox_map"; diff --git a/config/fudo/secure-dns-proxy.nix b/config/fudo/secure-dns-proxy.nix new file mode 100644 index 0000000..3f481e1 --- /dev/null +++ b/config/fudo/secure-dns-proxy.nix @@ -0,0 +1,62 @@ +{ lib, pkgs, config, ... }: + +with lib; +let + cfg = config.fudo.secure-dns-proxy; + +in { + options.fudo.secure-dns-proxy = { + enable = mkEnableOption "Enable a DNS server using an encrypted upstream source."; + + port = mkOption { + type = types.port; + description = "Port on which to listen for DNS queries."; + default = 53; + }; + + upstream-dns = mkOption { + type = with types; listOf str; + description = '' + The upstream DNS services to use, in a format useable by dnsproxy. + + See: https://github.com/AdguardTeam/dnsproxy + ''; + default = ["https://cloudflare-dns.com/dns-query"]; + }; + + bootstrap-dns = mkOption { + type = types.str; + description = "A simple DNS server from which HTTPS DNS can be bootstrapped, if necessary."; + default = "1.1.1.1"; + }; + + listen-ips = mkOption { + type = with types; listOf str; + description = "A list of local IP addresses on which to listen."; + default = ["0.0.0.0"]; + }; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ + dnsproxy + ]; + + systemd.services.secure-dns-proxy = { + enable = true; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + description = "DNS Proxy for secure DNS lookups"; + serviceConfig = let + upstreams = map (upstream: "-u ${upstream}") cfg.upstream-dns; + upstream-line = concatStringsSep " " upstreams; + listen-line = concatStringsSep " " + (map (listen: "-l ${listen}") cfg.listen-ips); + cmd = "${pkgs.dnsproxy}/bin/dnsproxy -p ${toString cfg.port} ${upstream-line} ${listen-line} -b ${cfg.bootstrap-dns}"; + + in { + ExecStart = cmd; + }; + }; + }; +} diff --git a/config/fudo/slynk.nix b/config/fudo/slynk.nix new file mode 100644 index 0000000..8ae12ed --- /dev/null +++ b/config/fudo/slynk.nix @@ -0,0 +1,70 @@ +{ config, lib, pkgs, ... }: + +with lib; +let + cfg = config.fudo.slynk; + + initScript = port: load-paths: let + load-path-string = + concatStringsSep " " (map (path: "\"${path}\"") load-paths); + in pkgs.writeText "slynk.lisp" '' + (load (merge-pathnames "quicklisp/setup.lisp" (user-homedir-pathname))) + (ql:quickload :slynk) + (setf asdf:*central-registry* + (append asdf:*central-registry* + (list ${load-path-string}))) + (slynk:create-server :port ${toString port} :dont-close t) + (dolist (var '("LD_LIBRARY_PATH")) + (format t "~S: ~S~%" var (sb-unix::posix-getenv var))) + + (loop (sleep 60)) + ''; + + lisp-libs = with pkgs.lispPackages; [ + alexandria + asdf-package-system + asdf-system-connections + cl_plus_ssl + cl-ppcre + quicklisp + quri + uiop + usocket + ]; + +in { + options.fudo.slynk = { + enable = mkEnableOption "Enable Slynk emacs common lisp server."; + + port = mkOption { + type = types.int; + description = "Port on which to open a Slynk server."; + default = 4005; + }; + }; + + config = mkIf cfg.enable { + systemd.user.services.slynk = { + description = "Slynk Common Lisp server."; + + serviceConfig = let + load-paths = (map (pkg: "${pkg}/lib/common-lisp/") lisp-libs); + in { + ExecStartPre = "${pkgs.lispPackages.quicklisp}/bin/quicklisp init"; + ExecStart = "${pkgs.sbcl}/bin/sbcl --load ${initScript cfg.port load-paths}"; + Restart = "on-failure"; + PIDFile = "/run/slynk.$USERNAME.pid"; + }; + + path = with pkgs; [ + gcc + glibc # for getent + file + ]; + + environment = { + LD_LIBRARY_PATH = "${pkgs.openssl_1_1.out}/lib"; + }; + }; + }; +} diff --git a/config/fudo/webmail.nix b/config/fudo/webmail.nix index d2c4f4e..cf93502 100644 --- a/config/fudo/webmail.nix +++ b/config/fudo/webmail.nix @@ -186,7 +186,7 @@ let }; password-file = mkOption { - type = types.path; + type = types.str; description = "Password to use when connecting to the database."; }; }; diff --git a/config/informis/cl-gemini.nix b/config/informis/cl-gemini.nix new file mode 100644 index 0000000..172a92b --- /dev/null +++ b/config/informis/cl-gemini.nix @@ -0,0 +1,212 @@ +{ config, lib, pkgs, ... }: + +with lib; +let + cfg = config.informis.cl-gemini; + + lisp-libs = with pkgs.lispPackages; [ + asdf-package-system + asdf-system-connections + alexandria + asdf-package-system + asdf-system-connections + cl_plus_ssl + cl-ppcre + quicklisp + quri + uiop + usocket + ]; + + launchServer = ip: port: root: public-dir: key: cert: slynk-port: feeds-string: textfiles-archive: + pkgs.writeText "launch-server.lisp" '' + (load (merge-pathnames "quicklisp/setup.lisp" (user-homedir-pathname))) + (ql:quickload :slynk) + (ql:quickload :cl-gemini) + ${optionalString (slynk-port != null) "(slynk:create-server :port ${toString slynk-port} :dont-close t)"} + ${feeds-string} + (cl-gemini:start-gemini-server "${ip}" "${key}" "${cert}" + :port ${toString port} + :document-root "${root}" + :textfiles-root "${textfiles-archive}" + :file-cmd "${pkgs.file}/bin/file" + :log-stream *standard-output* + :threaded t + :separate-thread t) + (loop (sleep 60)) + ''; + + sbcl-with-ssl = pkgs.sbcl.overrideAttrs (oldAttrs: rec { + extraLibs = with pkgs; [ + openssl_1_1.dev + ]; + }); + + feedOpts = with types; { + options = { + url = mkOption { + type = str; + description = "Base URI of the feed, i.e. the URI corresponding to the feed path."; + example = "gemini://my.server/path/to/feedfiles"; + }; + + title = mkOption { + type = str; + description = "Title of given feed."; + example = "My Fancy Feed"; + }; + + path = mkOption { + type = str; + description = "Path to Gemini files making up the feed."; + example = "/path/to/feed"; + }; + }; + }; + + register-feed = name: opts: '' + (cl-gemini:register-feed :name "${name}" :title "${opts.title}" :path "${opts.path}" :base-uri "${opts.url}")''; + + register-feeds = feeds: + concatStringsSep "\n" + (mapAttrsToList register-feed feeds); + + +in { + options.informis.cl-gemini = with types; { + enable = mkEnableOption "Enable the cl-gemini server."; + + port = mkOption { + type = port; + description = "Port on which to serve Gemini traffic."; + default = 1965; + }; + + server-ip = mkOption { + type = str; + description = "IP on which to serve Gemini traffic."; + example = "1.2.3.4"; + }; + + document-root = mkOption { + type = str; + description = "Root at which to look for gemini files."; + example = "/my/gemini/root"; + }; + + user-public = mkOption { + type = str; + description = "Subdirectory of user homes to check for gemini files."; + default = "gemini-public"; + }; + + ssl-private-key = mkOption { + type = path; + description = "Path to the pem-encoded server private key."; + example = /path/to/secret/key.pem; + }; + + ssl-certificate = mkOption { + type = path; + description = "Path to the pem-encoded server public certificate."; + example = /path/to/cert.pem; + }; + + slynk-port = mkOption { + type = nullOr port; + description = "Port on which to open a slynk server, if any."; + default = null; + }; + + feeds = mkOption { + type = loaOf (submodule feedOpts); + description = "Feeds to generate and make available (as eg. /feed/name.xml)."; + example = { + diary = { + title = "My Diary"; + path = "/path/to/my/gemfiles/"; + url = "gemini://my.host/blog-path/"; + }; + }; + default = {}; + }; + + textfiles-archive = mkOption { + type = str; + description = "A path containing only gemini & text files."; + example = "/path/to/textfiles/"; + }; + }; + + config = mkIf cfg.enable { + + environment.systemPackages = with pkgs; [ + cl-gemini + ]; + + users.users = { + cl-gemini = { + isSystemUser = true; + group = "nogroup"; + createHome = true; + home = "/var/lib/cl-gemini"; + }; + }; + + environment.etc = { + "cl-gemini/key.pem" = { + mode = "0400"; + user = "cl-gemini"; + source = cfg.ssl-private-key; + }; + + "cl-gemini/cert.pem" = { + mode = "0444"; + user = "cl-gemini"; + source = cfg.ssl-certificate; + }; + }; + + systemd.services.cl-gemini = { + description = "cl-gemini Gemini server (https://gemini.circumlunar.space/)"; + + serviceConfig = let + feed-registrations = register-feeds cfg.feeds; + in { + ExecStartPre = "${pkgs.lispPackages.quicklisp}/bin/quicklisp init"; + ExecStart = "${sbcl-with-ssl}/bin/sbcl --load ${ + launchServer + cfg.server-ip + cfg.port + cfg.document-root + cfg.user-public + "/etc/cl-gemini/key.pem" + "/etc/cl-gemini/cert.pem" + cfg.slynk-port + feed-registrations + cfg.textfiles-archive + }"; + Restart = "on-failure"; + PIDFile = "/run/cl-gemini.$USERNAME.uid"; + User = "cl-gemini"; + }; + + environment = { + LD_LIBRARY_PATH = "${pkgs.openssl_1_1.out}/lib"; + CL_SOURCE_REGISTRY = concatStringsSep ":" + (["${config.users.users.cl-gemini.home}/quicklisp/quicklisp"] ++ + (map + (pkg: "${pkg}//") + (lisp-libs ++ [pkgs.cl-gemini]))); + }; + + path = with pkgs; [ + gcc + file + getent + ]; + + wantedBy = [ "default.target" ]; + }; + }; +} diff --git a/config/local.nix b/config/local.nix index 0e3b01f..a3e43b2 100644 --- a/config/local.nix +++ b/config/local.nix @@ -7,19 +7,25 @@ with lib; ./fudo/authentication.nix ./fudo/chat.nix ./fudo/common.nix + ./fudo/dns.nix ./fudo/git.nix ./fudo/grafana.nix ./fudo/kdc.nix ./fudo/ldap.nix + ./fudo/local-network.nix ./fudo/mail.nix ./fudo/mail-container.nix ./fudo/minecraft-server.nix ./fudo/node-exporter.nix ./fudo/postgres.nix ./fudo/prometheus.nix + ./fudo/secure-dns-proxy.nix + ./fudo/slynk.nix ./fudo/system.nix ./fudo/webmail.nix + ./informis/cl-gemini.nix + ../fudo/profiles ../fudo/sites ]; diff --git a/configuration.nix b/configuration.nix deleted file mode 120000 index 4de3171..0000000 --- a/configuration.nix +++ /dev/null @@ -1 +0,0 @@ -./hosts/france.nix \ No newline at end of file diff --git a/defaults.nix b/defaults.nix index 8e9334b..c6099b9 100644 --- a/defaults.nix +++ b/defaults.nix @@ -18,6 +18,7 @@ autoconf automake bash + boot bind binutils btrfs-progs @@ -28,8 +29,11 @@ certbot clang curl + dpkg emacs + enca fail2ban + file fortune gcc git @@ -44,6 +48,7 @@ jdk kerberos libisofs + libstdcxxHook lispPackages.alexandria lispPackages.cl-ppcre lispPackages.clx @@ -51,6 +56,8 @@ lshw mkpasswd ncurses5 + nix-index + nix-prefetch-git nmap oidentd openldap @@ -58,6 +65,7 @@ openssl_1_1 openssh_gssapi pciutils + pinentry.curses pv pwgen racket @@ -73,19 +81,28 @@ unzip vim wget + yubikey-manager + yubikey-personalization ]; - system.stateVersion = "19.09"; + system.stateVersion = "20.03"; system.autoUpgrade.enable = true; environment.etc.current-nixos-config.source = ./.; krb5.enable = true; - krb5.libdefaults.default_realm = "FUDO.ORG"; krb5.kerberos = pkgs.heimdalFull; - console.keyMap = "dvp"; + services.xserver = { + layout = "us"; + xkbVariant = "dvp"; + xkbOptions = "ctrl:nocaps"; + }; + + console = { + useXkbConfig = true; + }; i18n = { defaultLocale = "en_US.UTF-8"; @@ -95,6 +112,8 @@ mosh.enable = true; ssh = { + startAgent = false; + extraConfig = '' GSSAPIAuthentication yes GSSAPIDelegateCredentials yes @@ -129,8 +148,21 @@ GSSAPICleanupCredentials yes ''; }; + + pcscd = { + enable = true; + }; + + udev.packages = with pkgs; [ + yubikey-personalization + ]; }; + environment.shellInit = '' + gpg-connect-agent /bye + export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket) + ''; + security.pam = { enableSSHAgentAuth = true; # TODO: add yubico? @@ -167,6 +199,9 @@ group = "users"; home = "/home/niten"; hashedPassword = "$6$a1q2Duoe35hd5$IaZGXPfqyGv9uq5DQm7DZq0vIHsUs39sLktBiBBqMiwl/f/Z4jSvNZLJp9DZJYe5u2qGBYh1ca.jsXvQA8FPZ/"; + openssh.authorizedKeys.keys = [ + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDoWkjyeIfgwm0b78weToVYOQSD0RQ0qbNzpsN5NokbIFv2/980kLtnYrQEgIJ/JwMLlT3uJYacbCT5/a6Fb8oLxNpj0AF1EKaWZ3Rrlg72Sq+9SEwJwWWmZizX83sovMwUBMaUp6jWLhAhPpzBW5pfc5YWoc89wxGbELSwzgt5EgHbSJgvDnaHSp3fVaY01wfDXbL/oO160iNe7wv2HLMZu/FkWBkIjz6HmoGJJzYM89bUpHbyYG28lmCHB/8UPog5/BsjOn3/qupgf4zh6mMdMsXLvbR2jVwVjxcEMj9N5nCvc+Y3oi7Mij6VNrWbhkaAJMEzeMhWYrF3/pFQxUqG37aK3d0gw9kp5tMDLIlAPX4y1lfA87pIzoa0+Alql0CJQA1IJvp9SFG7lBmSthWQLmZvwwfoGg/ZjF6rOgsVoZ8TizpQnydWJDr6NboU9LL9Oa64OM5Rs0AU3cR2UbOF4QIcWFJ/7oDe3dOnfZ8QYqx9eXJyxoAUpDanaaTHYBiAKkeOBwQU+MVLKCcONKw9FZclf/1TpDB5b3/JeUFANjHQTv0UXA4YYU7iCx6H7XB4qwwtU9O19CGQYYfCfULX12/fRpYJw6VJaQWyyU4Bn5dk/dcB2nGI36jwbLMfhbUTIApujioAnd/GQIMakHEZ1+syPhMx9BxMkZb99B0A1Q== openpgp:0x4EC95B64" + ]; }; reaper = { isNormalUser = true; diff --git a/fudo/profiles/common-ui.nix b/fudo/profiles/common-ui.nix new file mode 100644 index 0000000..424fbbf --- /dev/null +++ b/fudo/profiles/common-ui.nix @@ -0,0 +1,179 @@ +{ config, lib, pkgs, ... }: + +with lib; +let + profile = config.fudo.common.profile; + + common-packages = with pkgs; [ + ffmpeg-full + libfixposix + mono + nomacs + python37Packages.youtube-dl + sqlite + system-config-printer + ]; + + gui-packages = with pkgs; [ + cool-retro-term + corefonts + chrome-gnome-shell + chromium + evince + firefox + gimp + glxinfo + gnome3.gnome-shell + gnome3.gnome-session + google-chrome + gtk2 + gtk2-x11 + gtk3 + gtkimageview + i3lock + mplayer + mpv + pdftk + redshift + rhythmbox + shotwell + spotify + (steam.override { + nativeOnly = true; + extraPkgs = pkgs: [ + mono + fmodex + gtk3 + gtk3-x11 + libgdiplus + zlib + ]; + withJava = true; + }).run + virtmanager + xorg.xev + xzgv + virtmanager-qt + ]; + + cfg = config.fudo.common; + +in mkIf ((profile == "desktop") || (profile == "laptop")) { + environment.systemPackages = + common-packages ++ (if cfg.enable-gui then gui-packages else []); + + nixpkgs.config.allowBroken = true; + + services.avahi = { + enable = true; + browseDomains = [config.fudo.common.domain]; + domainName = config.fudo.common.domain; + }; + + # splash screen + boot.plymouth.enable = false; + + boot.tmpOnTmpfs = true; + + services.xserver = if cfg.enable-gui then { + enable = true; + + layout = "us"; + xkbVariant = "dvp"; + xkbOptions = "ctrl:nocaps"; + + desktopManager.gnome3.enable = true; + + displayManager.gdm.enable = true; + + displayManager.defaultSession = "gnome"; + + windowManager.session = pkgs.lib.singleton { + name = "stumpwm"; + start = '' + ${pkgs.lispPackages.stumpwm}/bin/stumpwm & + waidPID=$! + ''; + }; + } else { + layout = "us"; + xkbVariant = "dvp"; + xkbOptions = "ctrl:nocaps"; + }; + + services.gnome3 = mkIf cfg.enable-gui { + evolution-data-server.enable = pkgs.lib.mkForce false; + gnome-user-share.enable = pkgs.lib.mkForce false; + }; + + services.dbus.socketActivated = true; + + sound.enable = true; + + hardware.pulseaudio.enable = true; + + fonts = mkIf cfg.enable-gui { + enableFontDir = true; + #fontconfig.antialias = true; + fontconfig.enable = true; + #fontconfig.penultimate.enable = true; + #fontconfig.subpixel.lcdfilter = "default"; + + fonts = with pkgs; [ + cantarell_fonts + dejavu_fonts + dina-font + dosemu_fonts + fira-code + fira-code-symbols + freefont_ttf + liberation_ttf + mplus-outline-fonts + #nerdfonts + noto-fonts + noto-fonts-cjk + noto-fonts-emoji + proggyfonts + terminus_font + ubuntu_font_family + ucsFonts + ultimate-oldschool-pc-font-pack + unifont + vistafonts + xlibs.fontadobe100dpi + xlibs.fontadobe75dpi + xlibs.fontadobeutopia100dpi + xlibs.fontadobeutopia75dpi + xlibs.fontadobeutopiatype1 + xlibs.fontarabicmisc + xlibs.fontbh100dpi + xlibs.fontbh75dpi + xlibs.fontbhlucidatypewriter100dpi + xlibs.fontbhlucidatypewriter75dpi + xlibs.fontbhttf + xlibs.fontbhtype1 + xlibs.fontbitstream100dpi + xlibs.fontbitstream75dpi + xlibs.fontbitstreamtype1 + xlibs.fontcronyxcyrillic + xlibs.fontcursormisc + xlibs.fontdaewoomisc + xlibs.fontdecmisc + xlibs.fontibmtype1 + xlibs.fontisasmisc + xlibs.fontjismisc + xlibs.fontmicromisc + xlibs.fontmisccyrillic + xlibs.fontmiscethiopic + xlibs.fontmiscmeltho + xlibs.fontmiscmisc + xlibs.fontmuttmisc + xlibs.fontschumachermisc + xlibs.fontscreencyrillic + xlibs.fontsonymisc + xlibs.fontsunmisc + xlibs.fontwinitzkicyrillic + xlibs.fontxfree86type1 + ]; + }; +} diff --git a/fudo/profiles/default.nix b/fudo/profiles/default.nix index e327416..a4c5892 100644 --- a/fudo/profiles/default.nix +++ b/fudo/profiles/default.nix @@ -2,7 +2,9 @@ { imports = [ + ./common-ui.nix ./desktop.nix + ./laptop.nix ./server.nix ]; } diff --git a/fudo/profiles/desktop.nix b/fudo/profiles/desktop.nix index 302fcc9..5d70746 100644 --- a/fudo/profiles/desktop.nix +++ b/fudo/profiles/desktop.nix @@ -1,154 +1,8 @@ { config, lib, pkgs, ... }: with lib; -{ - config = mkIf (config.fudo.common.profile == "desktop") { - environment.systemPackages = with pkgs; [ - cool-retro-term - chrome-gnome-shell - chromium - ffmpeg-full - firefox - gimp - glxinfo - gnome3.gnome-shell - gnome3.gnome-session - google-chrome - gtk2 - gtk2-x11 - gtk3 - gtkimageview - i3lock - libfixposix - minecraft - mplayer - nomacs - openssl_1_1 - redshift - rhythmbox - shotwell - spotify - sqlite - steam - system-config-printer - virtmanager - xorg.xev - xzgv - virtmanager-qt - ]; - - # Splash screen - boot.plymouth.enable = true; - - services.avahi = { - enable = true; - browseDomains = [config.fudo.domain]; - domainName = config.fudo.domain; - }; - - boot.tmpOnTmpfs = true; - - services.xserver = { - enable = true; - - layout = "us"; - xkbVariant = "dvp"; - xkbOptions = "ctrl:nocaps"; - - desktopManager.gnome3.enable = true; - desktopManager.default = "gnome3"; - - displayManager.gdm.enable = true; - - windowManager.session = pkgs.lib.singleton { - name = "stumpwm"; - start = '' - ${pkgs.lispPackages.stumpwm}/bin/stumpwm & - waidPID=$! - ''; - }; - }; - - services.printing = { - enable = true; - }; - - services.gnome3 = { - evolution-data-server.enable = pkgs.lib.mkForce false; - gnome-user-share.enable = pkgs.lib.mkForce false; - }; - - services.dbus.socketActivated = true; - - services.openssh.forwardX11 = true; - - programs.ssh.forwardX11 = true; - - sound.enable = true; - - hardware.pulseaudio.enable = true; - - fonts = { - enableCoreFonts = true; - enableFontDir = true; - enableGhostscriptFonts = false; - fontconfig.ultimate.enable = true; - - fonts = with pkgs; [ - cantarell_fonts - dejavu_fonts - dina-font - dosemu_fonts - fira-code - fira-code-symbols - freefont_ttf - liberation_ttf - mplus-outline-fonts - nerdfonts - noto-fonts - noto-fonts-cjk - noto-fonts-emoji - proggyfonts - terminus_font - ubuntu_font_family - ucsFonts - unifont - vistafonts - xlibs.fontadobe100dpi - xlibs.fontadobe75dpi - xlibs.fontadobeutopia100dpi - xlibs.fontadobeutopia75dpi - xlibs.fontadobeutopiatype1 - xlibs.fontarabicmisc - xlibs.fontbh100dpi - xlibs.fontbh75dpi - xlibs.fontbhlucidatypewriter100dpi - xlibs.fontbhlucidatypewriter75dpi - xlibs.fontbhttf - xlibs.fontbhtype1 - xlibs.fontbitstream100dpi - xlibs.fontbitstream75dpi - xlibs.fontbitstreamtype1 - xlibs.fontcronyxcyrillic - xlibs.fontcursormisc - xlibs.fontdaewoomisc - xlibs.fontdecmisc - xlibs.fontibmtype1 - xlibs.fontisasmisc - xlibs.fontjismisc - xlibs.fontmicromisc - xlibs.fontmisccyrillic - xlibs.fontmiscethiopic - xlibs.fontmiscmeltho - xlibs.fontmiscmisc - xlibs.fontmuttmisc - xlibs.fontschumachermisc - xlibs.fontscreencyrillic - xlibs.fontsonymisc - xlibs.fontsunmisc - xlibs.fontwinitzkicyrillic - xlibs.fontxfree86type1 - ]; - }; +mkIf (config.fudo.common.profile == "desktop") { + networking = { + networkmanager.enable = mkForce false; }; } diff --git a/fudo/profiles/laptop.nix b/fudo/profiles/laptop.nix new file mode 100644 index 0000000..dfb41fc --- /dev/null +++ b/fudo/profiles/laptop.nix @@ -0,0 +1,41 @@ +{ config, lib, pkgs, ... }: + +with lib; + +{ + options.fudo.laptop = { + use-network-manager = mkOption { + type = types.bool; + description = "Use NetworkManager instead of wpa_supplicant."; + default = false; + }; + }; + config = mkIf (config.fudo.common.profile == "laptop") { + environment.systemPackages = with pkgs; [ + acpi + upower + wpa_supplicant + ]; + + networking = if (config.fudo.laptop.use-network-manager) then { + networkmanager.enable = true; + } else { + networkmanager.enable = false; + wireless = { + enable = true; + userControlled = { + enable = true; + group = "wheel"; + }; + networks = { + "sea.fudo.org" = { + psk = "DahHaocheiD5"; + }; + "Pixel_9041" = { + psk = "ea72027e4e6"; + }; + }; + }; + }; + }; +} diff --git a/fudo/profiles/server.nix b/fudo/profiles/server.nix index 1011659..7e1b7c3 100644 --- a/fudo/profiles/server.nix +++ b/fudo/profiles/server.nix @@ -51,6 +51,8 @@ in { config = mkIf (config.fudo.common.profile == "server") { environment = { systemPackages = with pkgs; [ + ldns + ldns.examples test-config reboot-if-necessary ]; diff --git a/fudo/sites/default.nix b/fudo/sites/default.nix index 6caa1b3..fd59359 100644 --- a/fudo/sites/default.nix +++ b/fudo/sites/default.nix @@ -2,6 +2,7 @@ { imports = [ + ./joes.nix ./portage.nix ./seattle.nix ]; diff --git a/fudo/sites/joes.nix b/fudo/sites/joes.nix new file mode 100644 index 0000000..5fcb5a7 --- /dev/null +++ b/fudo/sites/joes.nix @@ -0,0 +1,55 @@ +{ config, lib, pkgs, ... }: + +with lib; +let + hostname = config.networking.hostName; + + gateway = "172.86.179.17"; + + local-domain = "informis.land"; + + admin = "admin@${local-domain}"; + +in { + config = mkIf (config.fudo.common.site == "joes") { + time.timeZone = "America/Winnipeg"; + + services.cron = { + mailto = admin; + }; + + networking = { + domain = local-domain; + search = [ local-domain "fudo.org" ]; + firewall.enable = false; + + defaultGateway = gateway; + # defaultGateway6 = gateway6; + + hosts = { + "127.0.0.1" = [ + "${config.networking.hostName}.${local-domain}" + config.networking.hostName + ]; + }; + }; + + krb5.libdefaults.default_realm = "INFORMIS.LAND"; + + fudo.node-exporter = { + enable = false; + hostname = hostname; + }; + + security.acme.certs."${hostname}.${local-domain}" = { + email = "admin@${local-domain}"; + }; + + services.nginx = { + enable = true; + recommendedGzipSettings = true; + recommendedOptimisation = true; + recommendedTlsSettings = true; + }; + }; +} diff --git a/fudo/sites/seattle.nix b/fudo/sites/seattle.nix index 12246a2..3a48232 100644 --- a/fudo/sites/seattle.nix +++ b/fudo/sites/seattle.nix @@ -6,24 +6,45 @@ let local-domain = "sea.fudo.org"; + gateway = "10.0.0.1"; + + nameservers = ["10.0.0.1"]; + in { config = mkIf (config.fudo.common.site == "seattle") { time.timeZone = "America/Los_Angeles"; + services.printing = { + enable = true; + }; + services.cron = { mailto = admin; }; + krb5.libdefaults.default_realm = "FUDO.ORG"; + networking = { domain = local-domain; search = [local-domain "fudo.org"]; firewall.enable = false; - networkmanager.enable = pkgs.lib.mkForce false; + nameservers = nameservers; - # Until Comcast gets it's shit together... :( - enableIPv6 = false; + # Don't set the gateway if we ARE the gateway. + # This is the most generic way I can think of to do that. local-network is really + # about running all the local servers (DNS, DHCP, and providing gateway). + defaultGateway = optionalString (config.fudo.local-network.enable != true) gateway; + + enableIPv6 = true; + + # Necessary to make sure than Kerberos and Avahi both work (the former + # needs the full reverse-lookup name of the server, the latter wants + # `hostname` to return just the host itself. + hosts = { + "127.0.0.1" = [ "${config.networking.hostName}.${local-domain}" config.networking.hostName]; + }; }; users.extraUsers = { @@ -53,6 +74,12 @@ in { home = "/home/xiaoxuan"; hashedPassword = "$6$C8lYHrK7KvdKm/RE$cHZ2hg5gEOEjTV8Zoayik8sz5h.Vh0.ClCgOlQn8l/2Qx/qdxqZ7xCsAZ1GZ.IEyESfhJeJbjLpykXDwPpfVF0"; }; + kevin = { + isNormalUser = true; + createHome = true; + home = "/home/kevin"; + hashedPassword = ""; + }; }; fileSystems."/mnt/documents" = { @@ -84,118 +111,207 @@ in { fsType = "nfs4"; }; - # Should use this eventually... - # fudo.localNetwork = { - # masterNameServer = { - # ip = "10.0.0.1"; - # ipReverseDomain = "0.10.in-addr.arpa"; - # }; + fudo.common.domain = "sea.fudo.org"; - # domain = "${local-domain}"; + fudo.local-network = { - # hostAliases = { - # kadmin = "slab"; - # kdc = "slab"; - # photo = "doraemon"; - # music = "doraemon"; - # panopticon = "hyperion"; - # hole = "dnshole"; - # ipfs = "nostromo"; - # }; + domain = "${local-domain}"; - # hosts = { - # slab = { - # ipv4Address = "10.0.0.1"; - # }; - # volsung = { - # ipv4Address = "10.0.0.106"; - # macAddress = "ac:bc:32:7b:75:a5"; - # }; - # nest = { - # ipv4Address = "10.0.0.176"; - # macAddress = "18:b4:30:16:7c:5a"; - # }; - # monolith = { - # ipv4Address = "10.0.0.100"; - # macAddress = "6c:62:6d:c8:b0:d8"; - # }; - # brother-wireless = { - # ipv4Address = "10.0.0.160"; - # macAddress = "c0:38:96:64:49:65"; - # }; - # doraemon = { - # ipv4Address = "10.0.0.52"; - # macAddress = "00:11:32:0a:06:c5"; - # }; - # lm = { - # ipv4Address = "10.0.0.21"; - # macAddress = "52:54:00:D8:34:92"; - # }; - # ubiquiti-wifi = { - # ipv4Address = "10.0.0.126"; - # macAddress = "04:18:d6:20:48:fb"; - # }; - # front-light = { - # ipv4Address = "10.0.0.221"; - # macAddress = "94:10:3e:48:94:ed"; - # }; - # ipad = { - # ipv4Address = "10.0.0.202"; - # macAddress = "9c:35:eb:48:6e:71"; - # }; - # chromecast-2 = { - # ipv4Address = "10.0.0.215"; - # macAddress = "a4:77:33:59:a2:ba"; - # }; - # taipan = { - # ipv4Address = "10.0.0.107"; - # macAddress = "52:54:00:34:c4:78"; - # }; - # dns-hole = { - # ipv4Address = "10.0.0.185"; - # macAddress = "b8:27:eb:b2:95:fd"; - # }; - # family-tv = { - # ipv4Address = "10.0.0.205"; - # macAddress = "84:a4:66:3a:b1:f8"; - # }; - # spark = { - # ipv4Address = "10.0.0.108"; - # macAddress = "78:24:af:04:f7:dd"; - # }; - # babycam = { - # ipv4Address = "10.0.0.206"; - # macAddress = "08:ea:40:59:5f:9e"; - # }; - # hyperion = { - # ipv4Address = "10.0.0.109"; - # macAddress = "52:54:00:33:46:de"; - # }; - # cargo = { - # ipv4Address = "10.0.0.50"; - # macAddress = "00:11:32:75:d8:b7"; - # }; - # cam-entrance = { - # ipv4Address = "10.0.0.31"; - # macAddress = "9c:8e:cd:0e:99:7b"; - # }; - # cam-driveway = { - # ipv4Address = "10.0.0.32"; - # macAddress = "9c:8e:cd:0d:3b:09"; - # }; - # cam-deck = { - # ipv4Address = "10.0.0.33"; - # macAddress = "9c:8e:cd:0e:98:c8"; - # }; - # nostromo = { - # ipv4Address = "10.0.0.2"; - # macAddress = "14:fe:b5:ca:a2:c9"; - # }; - # zbox = { - # ipv4Address = "10.0.0.110"; - # macAddress = "18:60:24:91:CC:27"; - # }; - # }; - # }; + aliases = { + kadmin = "slab"; + kdc = "slab"; + photo = "doraemon"; + music = "doraemon"; + panopticon = "hyperion"; + ipfs = "nostromo"; + hole = "nostromo"; + pihole = "nostromo"; + dns-hole = "nostromo"; + }; + + network = "10.0.0.0/16"; + + dhcp-dynamic-network = "10.0.1.0/24"; + + enable-reverse-mappings = true; + + srv-records = { + tcp = { + domain = [{ + port = 53; + host = "nostromo.sea.fudo.org"; + }]; + kerberos = [{ + port = 88; + host = "france.fudo.org"; + }]; + kerberos-adm = [{ + port = 88; + host = "france.fudo.org"; + }]; + ssh = [{ + port = 22; + host = "nostromo.sea.fudo.org"; + }]; + ldap = [{ + port = 389; + host = "france.fudo.org"; + }]; + }; + + udp = { + domain = [{ + port = 53; + host = "nostromo.sea.fudo.org"; + }]; + kerberos = [{ + port = 88; + host = "france.fudo.org"; + }]; + kerboros-master = [{ + port = 88; + host = "france.fudo.org"; + }]; + kpasswd = [{ + port = 464; + host = "france.fudo.org"; + }]; + }; + }; + + hosts = { + nostromo = { + ip-address = "10.0.0.1"; + mac-address = "46:54:76:06:f1:10"; + }; + lm = { + ip-address = "10.0.0.2"; + mac-address = "00:23:7d:e6:d9:ea"; + }; + switch-master = { + ip-address = "10.0.0.5"; + mac-address = "00:14:1C:B6:BB:40"; + }; + # lm = { + # ip-address = "10.0.0.21"; + # mac-address = "52:54:00:D8:34:92"; + # }; + cam-entrance = { + ip-address = "10.0.0.31"; + mac-address = "9c:8e:cd:0e:99:7b"; + }; + cam-driveway = { + ip-address = "10.0.0.32"; + mac-address = "9c:8e:cd:0d:3b:09"; + }; + cam-deck = { + ip-address = "10.0.0.33"; + mac-address = "9c:8e:cd:0e:98:c8"; + }; + cargo = { + ip-address = "10.0.0.50"; + mac-address = "00:11:32:75:d8:b7"; + }; + whitedwarf = { + ip-address = "10.0.0.51"; + mac-address = "00:11:32:12:14:1d"; + }; + doraemon = { + ip-address = "10.0.0.52"; + mac-address = "00:11:32:0a:06:c5"; + }; + android = { + ip-address = "10.0.0.81"; + mac-address = "00:16:3e:43:39:fc"; + }; + retro-wired = { + ip-address = "10.0.0.82"; + mac-address = "dc:a6:32:6b:57:43"; + }; + retro = { + ip-address = "10.0.0.83"; + mac-address = "dc:a6:32:6b:57:45"; + }; + monolith = { + ip-address = "10.0.0.100"; + mac-address = "6c:62:6d:c8:b0:d8"; + }; + taipan = { + ip-address = "10.0.0.107"; + mac-address = "52:54:00:34:c4:78"; + }; + spark = { + ip-address = "10.0.0.108"; + mac-address = "78:24:af:04:f7:dd"; + }; + hyperion = { + ip-address = "10.0.0.109"; + mac-address = "52:54:00:33:46:de"; + }; + zbox = { + ip-address = "10.0.0.110"; + mac-address = "02:dd:80:52:83:9b"; + }; + ubiquiti-wifi = { + ip-address = "10.0.0.126"; + mac-address = "04:18:d6:20:48:fb"; + }; + brother-wireless = { + ip-address = "10.0.0.160"; + mac-address = "c0:38:96:64:49:65"; + }; + nest = { + ip-address = "10.0.0.176"; + mac-address = "18:b4:30:16:7c:5a"; + }; + xixi-phone = { + ip-address = "10.0.0.193"; + mac-address = "48:43:7c:75:89:42"; + }; + ipad = { + ip-address = "10.0.0.202"; + mac-address = "9c:35:eb:48:6e:71"; + }; + cam-front = { + ip-address = "10.0.0.203"; + mac-address = "c4:d6:55:3e:b4:c3"; + }; + family-tv = { + ip-address = "10.0.0.205"; + mac-address = "84:a4:66:3a:b1:f8"; + }; + babycam = { + ip-address = "10.0.0.206"; + mac-address = "08:ea:40:59:5f:9e"; + }; + workphone = { + ip-address = "10.0.0.211"; + mac-address = "a8:8e:24:5c:12:67"; + }; + chromecast-2 = { + ip-address = "10.0.0.215"; + mac-address = "a4:77:33:59:a2:ba"; + }; + front-light = { + ip-address = "10.0.0.221"; + mac-address = "94:10:3e:48:94:ed"; + }; + + + # Storage network + node-1 = { + ip-address = "10.0.10.101"; + mac-address = "00:1e:06:36:81:cf"; + }; + node-2 = { + ip-address = "10.0.10.102"; + mac-address = "00:1e:06:36:ec:3e"; + }; + node-3 = { + ip-address = "10.0.10.103"; + mac-address = "00:1e:06:36:ec:4b"; + }; + }; + }; }; } diff --git a/hardware-configuration.nix b/hardware-configuration.nix deleted file mode 100644 index e6d1fe0..0000000 --- a/hardware-configuration.nix +++ /dev/null @@ -1,31 +0,0 @@ -# Do not modify this file! It was generated by ‘nixos-generate-config’ -# and may be overwritten by future invocations. Please make changes -# to /etc/nixos/configuration.nix instead. -{ config, lib, pkgs, ... }: - -{ - imports = - [ - ]; - - boot.initrd.availableKernelModules = [ "uhci_hcd" "ehci_pci" "ata_piix" "ahci" "usb_storage" "floppy" "sd_mod" "sr_mod" ]; - boot.initrd.kernelModules = [ "dm-snapshot" ]; - boot.kernelModules = [ "kvm-intel" ]; - boot.extraModulePackages = [ ]; - - fileSystems."/" = - { device = "/dev/disk/by-uuid/87833c39-299b-4e84-9854-beda4a8e0115"; - fsType = "ext4"; - }; - - fileSystems."/boot" = - { device = "/dev/disk/by-uuid/bfb464c0-c259-4c29-8e8f-b3011bd30c95"; - fsType = "ext4"; - }; - - swapDevices = - [ { device = "/dev/disk/by-uuid/ac0fe2b7-dd7a-4e86-aaa0-942acf3d541d"; } - ]; - - nix.maxJobs = lib.mkDefault 8; -} diff --git a/hosts/atom.nix b/hosts/atom.nix new file mode 100644 index 0000000..2a8aef4 --- /dev/null +++ b/hosts/atom.nix @@ -0,0 +1,46 @@ +{ config, pkgs, ... }: + +let + hostname = "atom"; + +in { + + imports = [ + ../defaults.nix + ../hardware-configuration.nix + ]; + + boot.loader.grub.enable = true; + boot.loader.grub.version = 2; + boot.loader.grub.device = "/dev/sda"; + + networking.hostName = hostname; + + environment.systemPackages = with pkgs; [ + glxinfo + hll2380dw-cups + usbutils + ]; + + fudo.common = { + profile = "laptop"; + site = "seattle"; + }; + + hardware.cpu.amd.updateMicrocode = true; + + programs = { + bash.enableCompletion = true; + }; + + fudo.laptop.use-network-manager = false; + fudo.common.enable-gui = false; + + hardware.opengl.driSupport32Bit = true; + hardware.opengl.extraPackages32 = with pkgs.pkgsi686Linux; [ libva ]; + hardware.opengl.driSupport = true; + + hardware.pulseaudio.support32Bit = true; + + hardware.bluetooth.enable = true; +} diff --git a/hosts/france.nix b/hosts/france.nix index 34c00ec..4158f01 100644 --- a/hosts/france.nix +++ b/hosts/france.nix @@ -239,11 +239,13 @@ in { state-directory = "${system-mail-directory}/var"; mail-directory = "${system-mail-directory}/mailboxes"; - dovecot.ldap-reader-dn = "cn=user_db_reader,dc=fudo,dc=org"; - dovecot.ldap-reader-passwd = fileContents /srv/ldap/secure/user_db.passwd; + dovecot.ldap = { + reader-dn = "cn=user_db_reader,dc=fudo,dc=org"; + reader-passwd = fileContents /srv/ldap/secure/user_db.passwd; - # FIXME: use SSL once I can figure out Acme SSL cert CA for LDAP. - dovecot.ldap-urls = [ "ldap://france.fudo.org" ]; + # FIXME: use SSL once I can figure out Acme SSL cert CA for LDAP. + server-urls = [ "ldap://france.fudo.org" ]; + }; clamav.enable = true; @@ -265,7 +267,7 @@ in { name = "webmail"; hostname = "localhost"; user = "webmail"; - password-file = /srv/webmail/secure/db.passwd; + password-file = "/srv/webmail/secure/db.passwd"; }; }; @@ -279,7 +281,7 @@ in { name = "webmail"; hostname = "localhost"; user = "webmail"; - password-file = /srv/webmail/secure/db.passwd; + password-file = "/srv/webmail/secure/db.passwd"; }; }; @@ -292,7 +294,7 @@ in { name = "webmail"; hostname = "localhost"; user = "webmail"; - password-file = /srv/webmail/secure/db.passwd; + password-file = "/srv/webmail/secure/db.passwd"; }; }; }; @@ -305,12 +307,12 @@ in { site-name = "Fudo Chat"; smtp-server = "france.fudo.org"; smtp-user = "chat"; - smtp-password-file = /srv/mattermost/secure/smtp.passwd; + smtp-password-file = "/srv/mattermost/secure/smtp.passwd"; database = { name = "mattermost"; hostname = "localhost"; user = "mattermost"; - password-file = /srv/mattermost/secure/db.passwd; + password-file = "/srv/mattermost/secure/db.passwd"; }; }; diff --git a/hosts/nostromo.nix b/hosts/nostromo.nix index 578b30e..fc8967b 100644 --- a/hosts/nostromo.nix +++ b/hosts/nostromo.nix @@ -1,7 +1,9 @@ -{ config, pkgs, ... }: +{ lib, config, pkgs, ... }: let - hostname = "nostromo.sea.fudo.org"; + hostname = "nostromo"; + host-internal-ip = "10.0.0.1"; + inherit (lib.strings) concatStringsSep; in { @@ -9,77 +11,214 @@ in { boot.loader.grub.enable = true; boot.loader.grub.version = 2; - boot.loader.grub.device = "/dev/sdb"; + boot.loader.grub.device = "/dev/sda"; + + hardware.bluetooth.enable = false; imports = [ ../defaults.nix - ../networks/sea.fudo.org.nix - ../profiles/server.nix ../hardware-configuration.nix - # ../profiles/services/local_nameserver.nix ]; - fudo.postgresql = { - enable = true; - ssl-private-key = "/srv/nostromo.sea.fudo.org/certs/private/privkey.pem"; - ssl-certificate = "/srv/nostromo.sea.fudo.org/certs/cert.pem"; - keytab = "/srv/nostromo.sea.fudo.org/keytabs/postgres.keytab"; + fudo.common = { + profile = "server"; + site = "seattle"; + }; - local-networks = [ - "10.0.0.1/24" - ]; + fudo.local-network = { + enable = true; + # See fudo/sites/seattle.nix for general settings + dns-servers = [ host-internal-ip ]; + gateway = host-internal-ip; + dhcp-interfaces = [ "intif0" ]; + dns-serve-ips = [ host-internal-ip "127.0.0.1" "127.0.1.1" ]; + # Using a pihole running in docker, see below + recursive-resolver = "${host-internal-ip} port 5353"; + # recursive-resolver = "1.1.1.1"; + server-ip = host-internal-ip; + }; + + fudo.slynk = { + enable = true; }; networking = { hostName = hostname; - defaultGateway = "10.0.0.1"; - - nameservers = [ "10.0.0.1" ]; - - # Turn off for hypervisor: dhcp by default everywhere is a fuckin pain. - dhcpcd.enable = false; + nameservers = [ host-internal-ip ]; # Create a bridge for VMs to use - macvlans.intlan0 = { - interface = "eno1"; - mode = "bridge"; + macvlans = { + intif0 = { + interface = "eno1"; + mode = "bridge"; + }; }; interfaces = { - intlan0 = { + eno1.useDHCP = false; + eno3.useDHCP = false; + eno4.useDHCP = false; + enp33s0f0.useDHCP = false; + enp33s0f1.useDHCP = false; + enp9s0f0.useDHCP = false; + enp9s0f1.useDHCP = false; + + eno2.useDHCP = true; + + intif0 = { + useDHCP = false; macAddress = "46:54:76:06:f1:10"; ipv4.addresses = [ { - address = "10.0.0.2"; - prefixLength = 23; + address = host-internal-ip; + prefixLength = 22; + } + { + address = "10.0.10.2"; + prefixLength = 24; } ]; }; }; + + nat = { + enable = true; + externalInterface = "eno2"; + internalInterfaces = ["intif0"]; + }; }; - hardware.bluetooth.enable = false; + fudo = { + postgresql = { + enable = true; + ssl-private-key = "/srv/nostromo/certs/private/privkey.pem"; + ssl-certificate = "/srv/nostromo/certs/cert.pem"; + keytab = "/srv/nostromo/keytabs/postgres.keytab"; + + local-networks = [ + "10.0.0.1/24" + "127.0.0.1/8" + ]; + }; + + secure-dns-proxy = { + enable = true; + port = 3535; + upstream-dns = [ + "https://cloudflare-dns.com/dns-query" + # "https://dns.adguard.com/dns-query" + ]; + bootstrap-dns = "1.1.1.1"; + }; + }; environment.systemPackages = with pkgs; [ - ipfs + dnsproxy libguestfs-with-appliance libvirt virtmanager ]; - virtualisation.libvirtd = { - enable = true; - qemuPackage = pkgs.qemu_kvm; - onShutdown = "shutdown"; + virtualisation = { + docker = { + enable = true; + autoPrune.enable = true; + enableOnBoot = true; + }; + + libvirtd = { + enable = true; + qemuPackage = pkgs.qemu_kvm; + onShutdown = "shutdown"; + }; }; - services.ipfs = { - enable = true; - enableGC = true; - autoMount = false; - defaultMode = "online"; - apiAddress = "/ip4/10.0.0.2/tcp/5001"; - gatewayAddress = "/ipv4/10.0.0.2/tcp/8080"; + docker-containers = { + pihole = { + image = "pihole/pihole:4.3.2-1"; + ports = [ + "5353:53/tcp" + "5353:53/udp" + "3080:80/tcp" + ]; + environment = { + ServerIP = host-internal-ip; + VIRTUAL_HOST = "dns-hole.sea.fudo.org"; + DNS1 = "1.1.1.1"; + DNS2 = "8.8.8.8"; + }; + volumes = [ + "/srv/pihole/etc-pihole/:/etc/pihole/" + "/srv/pihole/etc-dnsmasq.d/:/etc/dnsmasq.d/" + ]; + # TODO: DNS-over-HTTPS via cloudflared + # extraDockerOptions = [ + # "--dns=1.1.1.1" + # ]; + }; + }; + + services = { + dhcpd6.enable = false; + + # glusterfs = { + # enable = true; + # enableGlustereventsd = true; + # useRpcbind = true; + # }; + + nginx = { + enable = true; + + virtualHosts = { + "pihole.sea.fudo.org" = { + serverAliases = [ + "dns-hole.sea.fudo.org" + "hole.sea.fudo.org" + ]; + + locations."/" = { + proxyPass = "http://127.0.0.1:3080"; + + extraConfig = '' + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-By $server_addr:$server_port; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + ''; + }; + }; + }; + }; + + # ceph = { + # enable = true; + + # global = { + # clusterName = "sea-data"; + # clusterNetwork = "10.0.10.0/24"; + # fsid = "d443e192-896d-4102-a60f-f8f0777eb2a3"; + # monHost = "10.0.10.2"; + # monInitialMembers = "mon-1"; + # publicNetwork = "10.0.0.0/22"; + # }; + + # mds = { + # enable = true; + # daemons = ["srv-2"]; + # }; + + # mgr = { + # enable = true; + # daemons = ["srv-2"]; + # }; + + # mon = { + # enable = true; + # daemons = ["srv-2"]; + # }; + # }; }; } diff --git a/hosts/procul.nix b/hosts/procul.nix new file mode 100644 index 0000000..2276e63 --- /dev/null +++ b/hosts/procul.nix @@ -0,0 +1,285 @@ +{ config, pkgs, lib, ... }: + +with lib; +let + hostname = "procul"; + domain = "informis.land"; + mail-hostname = hostname; + host_ipv4 = "172.86.179.18"; + host-fqdn = "${hostname}.${domain}"; + all-hostnames = []; + + acme-private-key = hostname: "/var/lib/acme/${hostname}/key.pem"; + acme-certificate = hostname: "/var/lib/acme/${hostname}/fullchain.pem"; + acme-ca = "/etc/nixos/static/letsencryptauthorityx3.pem"; + + fudo-ca = "/etc/nixos/static/fudo_ca.pem"; + +in { + + boot.loader.grub = { + enable = true; + version = 2; + device = "/dev/sdb"; + }; + + imports = [ + ../hardware-configuration.nix + + ../defaults.nix + + ../informis/users.nix + ]; + + environment.systemPackages = with pkgs; [ + multipath-tools + ]; + + networking = { + hostName = hostname; + + # provided by secure-dns-proxy + nameservers = [ "127.0.0.1" ]; + + dhcpcd.enable = false; + useDHCP = false; + + # TODO: fix IPv6 + enableIPv6 = true; + + # Create a bridge for VMs to use + macvlans = { + extif0 = { + interface = "enp0s25"; + mode = "bridge"; + }; + }; + + interfaces = { + extif0 = { + # result of: + # echo $FQDN-extif|md5sum|sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:\1:\2:\3:\4:\5/' + macAddress = "02:e2:b7:db:e8:af"; + ipv4.addresses = [ + { + address = host_ipv4; + prefixLength = 29; + } + ]; + }; + }; + }; + + hardware.bluetooth.enable = false; + + users = { + users = { + gituser = { + isSystemUser = true; + group = "nogroup"; + }; + }; + }; + + fudo = { + + common = { + # Sets some server-common settings. See /etc/nixos/fudo/profiles/... + profile = "server"; + + # Sets some common site-specific settings: gateway, monitoring, etc. See /etc/nixos/fudo/sites/... + site = "joes"; + + domain = domain; + + admin-email = "admin@${domain}"; + + local-networks = [ + "172.86.179.16/29" + "208.81.1.128/28" + "208.81.3.112/28" + "172.17.0.0/16" + "127.0.0.0/8" + ]; + }; + + # Not all users need access to procul; don't allow LDAP-user access. + authentication.enable = false; + + auth.kdc = { + enable = true; + database-path = "/var/heimdal/heimdal"; + realm = "INFORMIS.LAND"; + mkey-file = "/srv/heimdal/secure/m-key"; + acl-file = "/etc/heimdal/kdc.acl"; + bind-addresses = [ + host_ipv4 + "127.0.0.1" + "127.0.1.1" + ]; + }; + + secure-dns-proxy = { + enable = true; + upstream-dns = [ "https://cloudflare-dns.com/dns-query" ]; + bootstrap-dns = "1.1.1.1"; + listen-ips = [ "127.0.0.1" ]; + port = 53; + }; + + dns = { + enable = true; + + dns-hosts = { + "ns1.informis.land" = "172.86.179.18"; + "ns2.informis.land" = "172.86.179.18"; + }; + + listen-ips = [host_ipv4]; + + domains = { + "informis.land" = import ../informis/informis.land.nix { + inherit host_ipv4 config; + }; + }; + }; + + mail-server = { + enable = true; + debug = true; + + domain = domain; + hostname = "${host-fqdn}"; + monitoring = false; + mail-user = "mailuser"; + mail-user-id = 525; + mail-group = "mailgroup"; + clamav.enable = true; + dkim.signing = true; + + dovecot = { + ssl-certificate = acme-certificate "imap.${domain}"; + ssl-private-key = acme-private-key "imap.${domain}"; + }; + + postfix = { + ssl-certificate = acme-certificate "smtp.${domain}"; + ssl-private-key = acme-private-key "smtp.${domain}"; + }; + + # This should NOT include the primary domain + local-domains = [ + host-fqdn + "smtp.${domain}" + ]; + + mail-directory = "/srv/mailserver/mail"; + state-directory = "/srv/mailserver/state"; + + trusted-networks = [ + "172.86.179.16/29" + "127.0.0.0/16" + ]; + + alias-users = { + root = ["niten"]; + postmaster = ["niten"]; + hostmaster = ["niten"]; + webmaster = ["niten"]; + system = ["niten"]; + admin = ["niten"]; + dmarc-report = ["niten"]; + }; + }; + + postgresql = { + enable = true; + ssl-certificate = (acme-certificate host-fqdn); + ssl-private-key = (acme-private-key host-fqdn); + keytab = "/srv/postgres/secure/postgres.keytab"; + + local-networks = [ + "172.86.179.16/29" + "127.0.0.0/16" + ]; + + users = { + gituser = { + password = fileContents "/srv/git/secure/db.passwd"; + databases = { + git = "ALL PRIVILEGES"; + }; + }; + }; + + databases = { + git = ["niten"]; + }; + }; + + git = { + enable = true; + hostname = "git.informis.land"; + site-name = "informis git"; + user = "gituser"; + repository-dir = /srv/git/repo; + state-dir = /srv/git/state; + database = { + user = "gituser"; + password-file = /srv/git/secure/db.passwd; + hostname = "127.0.0.1"; + name = "git"; + }; + }; + + acme = { + enable = true; + + admin-address = "admin@${domain}"; + + hostnames = [ + "informis.land" + "imap.informis.land" + "smtp.informis.land" + "gemini.informis.land" + ]; + }; + }; + + security.acme.certs.${host-fqdn}.email = "admin@${domain}"; + + services.nginx = { + enable = true; + + recommendedGzipSettings = true; + recommendedOptimisation = true; + recommendedTlsSettings = true; + + virtualHosts = { + "${host-fqdn}" = { + enableACME = true; + forceSSL = true; + }; + }; + }; + + informis.cl-gemini = { + enable = true; + + server-ip = host_ipv4; + document-root = "/srv/gemini/root"; + ssl-private-key = "/srv/gemini/private/key.pem"; + ssl-certificate = "/srv/gemini/private/cert.pem"; + slynk-port = 4005; + + textfiles-archive = "/srv/gemini/textfiles"; + + feeds = { + viator = { + title = "viator's phlog"; + path = "/home/viator/gemini-public/feed/"; + url = "gemini://informis.land/user/viator/feed/"; + }; + }; + }; +} diff --git a/hosts/zbox.nix b/hosts/zbox.nix index eb74542..aab9ea9 100644 --- a/hosts/zbox.nix +++ b/hosts/zbox.nix @@ -4,34 +4,87 @@ let hostname = "zbox"; in { - imports = [ - ../defaults.nix - ../networks/sea.fudo.org.nix - ../profiles/desktop.nix - ../hardware-configuration.nix - ]; - - environment.systemPackages = with pkgs; [ - glxinfo - ]; # Use the systemd-boot EFI boot loader. boot.loader.systemd-boot.enable = true; boot.loader.efi.canTouchEfiVariables = true; + imports = [ + ../defaults.nix + ../hardware-configuration.nix + ]; + + environment.systemPackages = with pkgs; [ + androidenv.androidPkgs_9_0.platform-tools + android-studio + dnsproxy + glxinfo + hll2380dw-cups + nodejs + signal-desktop + thunderbird + usbutils + ]; + + fudo.common = { + profile = "desktop"; + site = "seattle"; + enable-gui = true; + }; + + fudo.slynk = { + enable = true; + }; + hardware.cpu.intel.updateMicrocode = true; - programs.bash.enableCompletion = true; + programs = { + adb.enable = true; + bash.enableCompletion = true; + }; services.xserver = { videoDrivers = ["nvidia"]; - displayManager.gdm.wayland = false; + # displayManager.gdm.wayland = false; }; hardware.opengl.driSupport32Bit = true; + hardware.opengl.extraPackages32 = with pkgs.pkgsi686Linux; [ libva ]; hardware.opengl.driSupport = true; - networking.hostName = hostname; + hardware.pulseaudio.support32Bit = true; + + networking = { + hostName = hostname; + + macvlans = { + intif0 = { + interface = "eno1"; + mode = "bridge"; + }; + }; + + interfaces = { + eno1.useDHCP = false; + intif0 = { + macAddress = "02:dd:80:52:83:9b"; + useDHCP = false; + ipv4.addresses = [ + { + address = "10.0.0.110"; + prefixLength = 24; + } + ]; + }; + }; + }; hardware.bluetooth.enable = true; + + users.users.niten = { + extraGroups = ["adbusers"]; + }; + + virtualisation.lxd.enable = true; + } diff --git a/informis/informis.land.nix b/informis/informis.land.nix new file mode 100644 index 0000000..acce31d --- /dev/null +++ b/informis/informis.land.nix @@ -0,0 +1,98 @@ +{ host_ipv4, config }: + +{ + dnssec = true; + + mx = ["smtp.informis.land"]; + + hosts = { + procul = { + ip-addresses = [ "172.86.179.18" ]; + ssh-fingerprints = [ + "4 1 2a8e086d3589ce50b58c55bc35638af8da23988e" + "4 2 55a9f7c0addf08bb24c62ced954574db6e95eff38ee56d6a2cff312d20eb910e" + "1 1 d089902f60751b3d35b5329bf7b906df254d5fa7" + "1 2 8deebf42bbc40881a327f561bffd5d7bd328a4fc94d4e4ce8c502a9c6cbdfb92" + ]; + }; + }; + + default-host = "172.86.179.18"; + + srv-records = { + tcp = { + domain = [{ + host = "ns1.informis.land"; + port = 53; + }]; + ssh = [{ + host = "procul.informis.land"; + port = 22; + }]; + submission = [{ + host = "procul.informis.land"; + port = 587; + }]; + kerberos = [{ + host = "procul.informis.land"; + port = 88; + }]; + kerberos-adm = [{ + host = "procul.informis.land"; + port = 749; + }]; + imaps = [{ + host = "procul.informis.land"; + port = 993; + priority = 0; + }]; + pop3s = [{ + host = "procul.informis.land"; + port = 995; + priority = 10; + }]; + http = [{ + host = "procul.informis.land"; + port = 80; + }]; + https = [{ + host = "procul.informis.land"; + port = 443; + }]; + }; + + udp = { + domain = [{ + host = "ns1.informis.land"; + port = 53; + }]; + kerberos = [{ + host = "procul.informis.land"; + port = 88; + }]; + kerberos-master = [{ + host = "procul.informis.land"; + port = 88; + }]; + kpasswd = [{ + host = "procul.informis.land"; + port = 464; + }]; + }; + }; + + aliases = { + smtp = "procul.informis.land."; + imap = "procul.informis.land."; + gemini = "procul.informis.land."; + git = "procul.informis.land."; + }; + + extra-dns-records = [ + ''_kerberos IN TXT "INFORMIS.LAND"'' + ''@ IN TXT "v=spf1 mx ip4:${host_ipv4}/29 -all"'' + ''@ IN SPF "v=spf1 mx ip4:${host_ipv4}/29 -all"'' + ]; + + dmarc-report-address = "dmarc-report@informis.land"; +} diff --git a/informis/users.nix b/informis/users.nix new file mode 100644 index 0000000..b858c6e --- /dev/null +++ b/informis/users.nix @@ -0,0 +1,14 @@ +{ config, ... }: + +{ + config = { + users.users = { + viator = { + isNormalUser = true; + description = "Viator"; + createHome = true; + hashedPassword = "$6$a1q2Duoe35hd5$IaZGXPfqyGv9uq5DQm7DZq0vIHsUs39sLktBiBBqMiwl/f/Z4jSvNZLJp9DZJYe5u2qGBYh1ca.jsXvQA8FPZ/"; + }; + }; + }; +} diff --git a/lib/ip.nix b/lib/ip.nix new file mode 100644 index 0000000..7f55bf0 --- /dev/null +++ b/lib/ip.nix @@ -0,0 +1,56 @@ +{ lib }: + +with lib; +let + joinString = lib.concatStringsSep; + + pow = x: e: if (e == 0) then 1 else x * (pow x (e - 1)); + +in rec { + + generateNBits = n: let + helper = n: c: if (c == n) then pow 2 c else (pow 2 c) + (helper n (c + 1)); + in if (n <= 0) then throw "Can't generate 0 or fewer bits" else helper (n - 1) 0; + + reverseIpv4 = ip: joinString "." (reverseList (splitString "." ip)); + + intToBinaryList = int: let + helper = int: cur: let + curExp = pow 2 cur; + in if (curExp > int) then + [] + else + [(if ((bitAnd curExp int) > 0) then 1 else 0)] ++ (helper int (cur + 1)); + in reverseList (helper int 0); + + leftShift = int: n: int * (pow 2 n); + + rightShift = int: n: int / (pow 2 n); + + ipv4ToInt = ip: let + els = map toInt (reverseList (splitString "." ip)); + in foldr (a: b: a + b) 0 (imap0 (i: el: (leftShift el (i * 8))) els); + + intToIpv4 = int: joinString "." (map (i: toString (bitAnd (rightShift int (i * 8)) 255)) [ 3 2 1 0 ]); + + rightPadBits = int: bits: bitOr int (generateNBits bits); + + maskFromV32Network = network: let + fullMask = ipv4ToInt "255.255.255.255"; + insignificantBits = 32 - (getNetworkMask network); + in intToIpv4 (leftShift (rightShift fullMask insignificantBits) insignificantBits); + + getNetworkMask = network: toInt (elemAt (splitString "/" network) 1); + + getNetworkBase = network: let + ip = elemAt (splitString "/" network) 0; + insignificantBits = 32 - (getNetworkMask network); + in intToIpv4 (leftShift (rightShift (ipv4ToInt ip) insignificantBits) insignificantBits); + + networkMinIp = network: intToIpv4 (1 + (ipv4ToInt (getNetworkBase network))); + + networkMaxIp = network: intToIpv4 (rightPadBits (ipv4ToInt (getNetworkBase network)) (32 - (getNetworkMask network))); + + # To avoid broadcast IP... + networkMaxButOneIp = network: intToIpv4 ((rightPadBits (ipv4ToInt (getNetworkBase network)) (32 - (getNetworkMask network))) - 1); +} diff --git a/packages/cl-gemini.nix b/packages/cl-gemini.nix new file mode 100644 index 0000000..22ef42c --- /dev/null +++ b/packages/cl-gemini.nix @@ -0,0 +1,24 @@ +{ stdenv, fetchgit, pkgs }: + +let + url = "https://git.informis.land/viator/cl-gemini.git"; + version = "0.1"; + +in stdenv.mkDerivation { + name = "cl-gemini-${version}"; + + src = fetchgit { + url = "https://git.informis.land/viator/cl-gemini.git"; + rev = "3de4d1945fc91d0c9f8f65a5d4b0d3f39fbdaa80"; + sha256 = "1xzfsp5vp36a3yas5pnhj2a1dd72r72fw3l49ah19hvbapaikhsw"; + fetchSubmodules = false; + }; + + phases = ["installPhase"]; + + installPhase = '' + mkdir -p "$out/lib/common-lisp/cl-gemini" + cp "$src/cl-gemini.asd" "$out/lib/common-lisp/cl-gemini" + cp -R "$src/src" "$out/lib/common-lisp/cl-gemini" + ''; +} diff --git a/packages/hll2380dw-cups.nix b/packages/hll2380dw-cups.nix new file mode 100644 index 0000000..b0ed6dd --- /dev/null +++ b/packages/hll2380dw-cups.nix @@ -0,0 +1,40 @@ +{ stdenv, fetchurl, makeWrapper, cups, dpkg, a2ps, ghostscript, gnugrep, gnused, coreutils, file, perl, which }: + +stdenv.mkDerivation rec { + pname = "hll2380dw-cups"; + version = "3.2.0-1"; + platform = "i386"; + + src = fetchurl { + url = "https://download.brother.com/welcome/dlf101772/hll2380dwcupswrapper-${version}.i386.deb"; + sha256 = "08g3kx5lgwzb3f9ypj8knmpkkj0h3kv1i4gd20rzjxrx6vx1wbpl"; + }; + + nativeBuildInputs = [ makeWrapper ]; + buildInputs = [ cups ghostscript dpkg a2ps ]; + + dontUnpack = true; + + installPhase = '' + dpkg-deb -x $src $out + wrapProgram $out/opt/brother/Printers/HLL2380DW/cupswrapper/paperconfigml1 \ + --prefix PATH : ${stdenv.lib.makeBinPath [ + coreutils ghostscript gnugrep gnused + ]} + mkdir -p $out/lib/cups/filter/ + ln -s $out/opt/brother/Printers/HLL2380DW/cupswrapper/brother_lpdwrapper_HLL2380W \ + $out/lib/cups/filter/brother_lpdwrapper_HLL2380DW + ln -s $out/opt/brother/Printers/HLL2380DW/paperconfigml1 \ + $out/lib/cups/filter/ + mkdir -p $out/share/cups/model + ln -s $out/opt/brother/Printers/HLL2380DW/cupswrapper/brother-HLL2380DW-cups-en.ppd $out/share/cups/model/ + ''; + + meta = with stdenv.lib; { + homepage = http://www.brother.com/; + description = "Brother HL-L2380DW combined print driver"; + license = licenses.unfree; + platforms = [ "x86_64-linux" ]; + downloadPage = http://support.brother.com/g/b/downloadlist.aspx?c=us_ot&lang=en&prod=hll2380dw_us&os=128; + }; +} diff --git a/packages/local.nix b/packages/local.nix index 60725a8..7e72555 100644 --- a/packages/local.nix +++ b/packages/local.nix @@ -15,11 +15,10 @@ }; }); - minecraft-server_1_15_2 = pkgs.minecraft-server.overrideAttrs (oldAttrs: rec { - version = "1.15.2"; + minecraft-current = pkgs.minecraft.overrideAttrs (oldAttrs: rec { src = builtins.fetchurl { - url = "https://launcher.mojang.com/v1/objects/bb2b6b1aefcd70dfd1892149ac3a215f6c636b07/server.jar"; - sha256 = "12kynrpxgcdg8x12wcvwkxka0fxgm5siqg8qq0nnmv0443f8dkw0"; + url = "https://launcher.mojang.com/download/Minecraft.tar.gz"; + sha256 = "1k9gf1v1law4kiz8f7i2fxkj5vq2cm37b3ys95zpyf4aiw5nzg33"; }; }); @@ -45,5 +44,15 @@ configureFlags = oldAttrs.configureFlags ++ [ "--with-gssapi" ]; buildInputs = oldAttrs.buildInputs ++ [ pkgs.krb5 ]; }); + + hll2380dw-cups = import ./hll2380dw-cups.nix { + inherit (pkgs) stdenv fetchurl makeWrapper cups dpkg a2ps ghostscript gnugrep gnused coreutils file perl which; + }; + + cl-gemini = import ./cl-gemini.nix { + pkgs = pkgs; + stdenv = pkgs.stdenv; + fetchgit = pkgs.fetchgit; + }; }; } diff --git a/static/niten@fudo.org.pubkey b/static/niten@fudo.org.pubkey new file mode 100644 index 0000000..08d57bb --- /dev/null +++ b/static/niten@fudo.org.pubkey @@ -0,0 +1,109 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +qCVCWa+H/Pev226LSn2PVKK5AlBbs6uSNx/Phg== +=8Fyj +-----END PGP PUBLIC KEY BLOCK-----