merged atom with upstream

This commit is contained in:
root 2020-07-16 15:09:33 -07:00
commit ac623c51c2
22 changed files with 1278 additions and 324 deletions

View File

@ -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 = ''
<html>
<head>
<title>${hostname}</title>
</head>
<body>
<h1>${hostname}</title>
</body>
</html>
'';
destination = "/www";
};
# text = ''
# <html>
# <head>
# <title>${hostname}</title>
# </head>
# <body>
# <h1>${hostname}</title>
# </body>
# </html>
# '';
# 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);
};
}

View File

@ -52,5 +52,7 @@ with lib;
description = "Email for administrator of this system.";
default = "admin@fudo.org";
};
enable-gui = mkEnableOption "Install desktop GUI software.";
};
}

218
config/fudo/dns.nix Normal file
View File

@ -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;
};
};
}

View File

@ -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"];
};
};
};
};
}

View File

@ -23,61 +23,86 @@ 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}
'';
dovecot-user = config.services.dovecot2.user;
ldap-passwd-entry = ldap-config: ''
passdb {
driver = ldap
args = ${ldap-conf "ldap-passdb.conf" ldap-config}
}
'';
in {
options.fudo.mail-server.dovecot = {
ssl-private-key = mkOption {
type = types.str;
description = "Location of the server SSL private key.";
};
ssl-certificate = mkOption {
type = types.str;
description = "Location of the server SSL certificate.";
};
ldap-ca = mkOption {
type = types.str;
ldapOpts = with types; {
ca = mkOption {
type = 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.";
server-urls = mkOption {
type = listOf str;
description = "A list of LDAP server URLs used for authentication.";
};
ldap-reader-dn = mkOption {
type = types.str;
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.
'';
};
ldap-reader-passwd = mkOption {
type = types.str;
reader-pw = 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 = with types; {
ssl-private-key = mkOption {
type = str;
description = "Location of the server SSL private key.";
};
ssl-certificate = mkOption {
type = str;
description = "Location of the server SSL certificate.";
};
ldap = mkOption {
type = nullOr (submodule ldapOpts);
default = null;
description = ''
LDAP auth server configuration. If omitted, the server will use local authentication.
'';
};
};
config = mkIf cfg.enable {
services.prometheus.exporters.dovecot = mkIf cfg.monitoring {
@ -93,8 +118,7 @@ in {
enableImap = true;
enableLmtp = true;
enablePop3 = true;
enablePAM = false;
enablePAM = cfg.dovecot.ldap == null;
createMailUser = true;
@ -124,14 +148,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 +196,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-conf cfg.dovecot.ldap)}
userdb {
driver = static
args = uid=${toString cfg.mail-user-id} home=${cfg.mail-directory}/%u
@ -189,8 +215,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 +272,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}
'';
};
}

View File

@ -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";

View File

@ -4,9 +4,15 @@ with lib;
let
cfg = config.fudo.slynk;
initScript = port: pkgs.writeText "slynk.lisp" ''
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)))
@ -14,6 +20,18 @@ let
(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.";
@ -29,24 +47,24 @@ in {
systemd.user.services.slynk = {
description = "Slynk Common Lisp server.";
serviceConfig = {
serviceConfig = let
load-paths = (map (pkg: "${pkg}/lib/common-lisp/") lisp-libs);
in {
ExecStartPre = "${pkgs.lispPackages.quicklisp}/bin/quicklisp init";
ExecStart = "${pkgs.sbcl-with-libs}/bin/sbcl --load ${initScript cfg.port}";
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:${pkgs.libuv.out}/lib";
LD_LIBRARY_PATH = "${pkgs.openssl_1_1.out}/lib";
};
## Starts on login. But what about ports?
# wantedBy = [ "default.target" ];
};
};
}

View File

@ -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" ];
};
};
}

View File

@ -7,6 +7,7 @@ with lib;
./fudo/authentication.nix
./fudo/chat.nix
./fudo/common.nix
./fudo/dns.nix
./fudo/git.nix
./fudo/grafana.nix
./fudo/kdc.nix
@ -23,6 +24,8 @@ with lib;
./fudo/system.nix
./fudo/webmail.nix
./informis/cl-gemini.nix
../fudo/profiles
../fudo/sites
];

View File

@ -31,6 +31,7 @@
curl
dpkg
emacs
enca
fail2ban
file
fortune
@ -56,6 +57,7 @@
mkpasswd
ncurses5
nix-index
nix-prefetch-git
nmap
oidentd
openldap
@ -80,6 +82,7 @@
vim
wget
yubikey-manager
yubikey-personalization
];
system.stateVersion = "20.03";
@ -89,7 +92,6 @@
environment.etc.current-nixos-config.source = ./.;
krb5.enable = true;
krb5.libdefaults.default_realm = "FUDO.ORG";
krb5.kerberos = pkgs.heimdalFull;
services.xserver = {
@ -110,6 +112,8 @@
mosh.enable = true;
ssh = {
startAgent = false;
extraConfig = ''
GSSAPIAuthentication yes
GSSAPIDelegateCredentials yes
@ -143,8 +147,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?
@ -180,6 +197,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;

View File

@ -4,206 +4,175 @@ with lib;
let
profile = config.fudo.common.profile;
in {
options.fudo.ui = {
console-only = mkOption {
type = types.bool;
description = "Don't install X, only console stuff.";
default = false;
};
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;
};
config = mkIf ((profile == "desktop") || (profile == "laptop")) {
environment.systemPackages = with pkgs;
[
libfixposix
python37Packages.youtube-dl
sqlite
virtmanager
xzgv
] ++ (if (config.fudo.ui.console-only) then [] else [
cool-retro-term
corefonts
chrome-gnome-shell
chromium
evince
ffmpeg-full
firefox
gimp
glxinfo
gnome3.gnome-shell
gnome3.gnome-session
google-chrome
gtk2
gtk2-x11
gtk3
gtkimageview
i3lock
libfixposix
#minecraft-current
mono
mplayer
mpv
nomacs
openssl_1_1
pdftk
python37Packages.youtube-dl
redshift
rhythmbox
shotwell
spotify
sqlite
(steam.override {
nativeOnly = true;
extraPkgs = pkgs: [
mono
gtk3
gtk3-x11
libgdiplus
zlib
];
withJava = true;
})
(steam.override {
nativeOnly = true;
extraPkgs = pkgs: [
mono
fmodex
gtk3
gtk3-x11
libgdiplus
zlib
];
withJava = true;
}).run
# steam-run
# steam-run-native
system-config-printer
virtmanager
xorg.xev
xzgv
virtmanager-qt
]);
# splash screen
boot.plymouth.enable = false;
nixpkgs.config.allowBroken = true;
boot.tmpOnTmpfs = true;
# Splash screen
boot.plymouth.enable =
if (config.fudo.ui.console-only) then false else true;
services.xserver = if cfg.enable-gui then {
enable = true;
services.avahi = {
enable = true;
browseDomains = [config.fudo.common.domain];
domainName = config.fudo.common.domain;
};
layout = "us";
xkbVariant = "dvp";
xkbOptions = "ctrl:nocaps";
boot.tmpOnTmpfs = true;
desktopManager.gnome3.enable = true;
services.xserver = if (config.fudo.ui.console-only) then {
enable = false;
displayManager.gdm.enable = true;
layout = "us";
xkbVariant = "dvp";
xkbOptions = "ctrl:nocaps";
} else {
enable = true;
displayManager.defaultSession = "gnome";
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 &
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 (! config.fudo.ui.console-only) {
evolution-data-server.enable = pkgs.lib.mkForce false;
gnome-user-share.enable = pkgs.lib.mkForce false;
};
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;
services.dbus.socketActivated = true;
#services.openssh.forwardX11 = true;
sound.enable = true;
#programs.ssh.forwardX11 = true;
hardware.pulseaudio.enable = true;
sound.enable = true;
fonts = mkIf cfg.enable-gui {
enableFontDir = true;
#fontconfig.antialias = true;
fontconfig.enable = true;
#fontconfig.penultimate.enable = true;
#fontconfig.subpixel.lcdfilter = "default";
hardware.pulseaudio.enable = true;
fonts = mkIf (! config.fudo.ui.console-only) {
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
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
];
};
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
];
};
}

View File

@ -51,6 +51,8 @@ in {
config = mkIf (config.fudo.common.profile == "server") {
environment = {
systemPackages = with pkgs; [
ldns
ldns.examples
test-config
reboot-if-necessary
];

View File

@ -2,17 +2,14 @@
with lib;
let
admin = "admin@fudo.org";
nameservers = [
"1.1.1.1"
"2606:4700:4700::1111"
];
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";
@ -22,31 +19,32 @@ in {
};
networking = {
domain = "fudo.org";
search = ["fudo.org"];
domain = local-domain;
search = [ local-domain "fudo.org" ];
firewall.enable = false;
nameservers = nameservers;
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 = true;
enable = false;
hostname = hostname;
};
security.acme.certs.${hostname} = {
email = "admin@fudo.org";
# plugins = [
# "fullchain.pem"
# "full.pem"
# "key.pem"
# "chain.pem"
# "cert.pem"
# ];
security.acme.certs."${hostname}.${local-domain}" = {
email = "admin@${local-domain}";
};
services.nginx = {
enable = true;
recommendedGzipSettings = true;

View File

@ -24,6 +24,8 @@ in {
mailto = admin;
};
krb5.libdefaults.default_realm = "FUDO.ORG";
networking = {
domain = local-domain;
search = [local-domain "fudo.org"];

View File

@ -34,7 +34,7 @@ in {
};
fudo.laptop.use-network-manager = false;
fudo.ui.console-only = true;
fudo.common.enable-gui = false;
hardware.opengl.driSupport32Bit = true;
hardware.opengl.extraPackages32 = with pkgs.pkgsi686Linux; [ libva ];

View File

@ -3,8 +3,10 @@
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";
@ -25,37 +27,20 @@ in {
../hardware-configuration.nix
../defaults.nix
../informis/users.nix
];
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";
local-networks = [
"172.86.179.18/29"
"208.81.1.128/28"
"208.81.3.112/28"
"172.17.0.0/16"
"127.0.0.0/8"
];
};
environment.systemPackages = with pkgs; [
multipath-tools
];
# Not all users need access to procul; don't allow LDAP-user access.
fudo.authentication.enable = false;
# TODO: not used yet
fudo.acme.hostnames = all-hostnames;
networking = {
hostName = hostname;
# provided by secure-dns-proxy
nameservers = [ "127.0.0.1" ];
dhcpcd.enable = false;
useDHCP = false;
@ -86,4 +71,215 @@ in {
};
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/";
};
};
};
}

View File

@ -29,6 +29,7 @@ in {
fudo.common = {
profile = "desktop";
site = "seattle";
enable-gui = true;
};
fudo.slynk = {

View File

@ -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";
}

14
informis/users.nix Normal file
View File

@ -0,0 +1,14 @@
{ config, ... }:
{
config = {
users.users = {
viator = {
isNormalUser = true;
description = "Viator";
createHome = true;
hashedPassword = "$6$a1q2Duoe35hd5$IaZGXPfqyGv9uq5DQm7DZq0vIHsUs39sLktBiBBqMiwl/f/Z4jSvNZLJp9DZJYe5u2qGBYh1ca.jsXvQA8FPZ/";
};
};
};
}

24
packages/cl-gemini.nix Normal file
View File

@ -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"
'';
}

View File

@ -32,14 +32,14 @@
buildInputs = oldAttrs.buildInputs ++ [ pkgs.krb5 ];
});
sbcl-with-libs = pkgs.sbcl.overrideAttrs (oldAttrs: rec {
extraLibs = with pkgs; [
openssl_1_1.dev
];
});
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;
};
};
}

View File

@ -0,0 +1,109 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
qCVCWa+H/Pev226LSn2PVKK5AlBbs6uSNx/Phg==
=8Fyj
-----END PGP PUBLIC KEY BLOCK-----